Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bal 3521 (WIP DO NOT MERGE) #3015

Open
wants to merge 9 commits into
base: dev
Choose a base branch
from
Open

Bal 3521 (WIP DO NOT MERGE) #3015

wants to merge 9 commits into from

Conversation

chesterkmr
Copy link
Collaborator

@chesterkmr chesterkmr commented Jan 31, 2025

Summary by CodeRabbit

  • New Features

    • Introduced new dynamic form sections for managing grouped entities (e.g., UBOS and Directors) to streamline data entry.
    • Expanded document management capabilities, enabling users to create, update, re-upload, and delete documents directly.
    • Added a new service for managing document files, enhancing file-related operations.
    • Implemented a custom hook for synchronizing entity states with dynamic forms.
  • Improvements

    • Enhanced overall validations and synchronization across dynamic forms and workflow operations for a smoother user experience.
    • Improved error handling in file uploads and document management processes.

Copy link

changeset-bot bot commented Jan 31, 2025

⚠️ No Changeset found

Latest commit: 4b381ba

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor

coderabbitai bot commented Jan 31, 2025

Walkthrough

This PR introduces extensive changes across both the UI and server services. In the UI, new hooks, utilities, and components were added or refactored for HTTP requests, dynamic form management, entity field grouping, file uploads, and document fields. In the backend, new document management functionality was implemented including new database migrations, models, enums, controllers, services, repositories, and DTOs, while legacy document handling logic was removed. Additionally, file management and storage modules were updated and business controllers were streamlined by removing document fields.

Changes

File(s) Change Summary
packages/ui/package.json Added dependency "sonner": "^1.4.3".
packages/ui/src/common/hooks/useHttp/** Introduced new hook “useHttp” with related types, utils (formatHeaders, request) and comprehensive unit tests.
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/** Added new components (EntityFieldGroup, UbosFieldGroup, DirectorsFieldGroup), providers, hooks (useEntitySync, useEntityFieldsIsValid), helpers (buildDocumentsCreationPayload, getEntityGroupValueDestination, transform, updateEntities) and updated exports, stories and tests.
packages/ui/src/components/organisms/Form/DynamicForm/fields/FileField/** Updated FileField interface to require uploadSettings, refactored file upload logic in useFileUpload, removed legacy helper functions and updated associated tests.
packages/ui/src/components/organisms/Form/Validator/** Added new documentValidator and updated TBaseValidators to include "document", plus accompanying unit tests and re-exports.
services/workflows-service/prisma/** Multiple SQL migrations: created new ENUMs, Document and DocumentFile tables, added projectId and issuingCountry columns, updated foreign key constraints with cascade rules, and adjusted Prisma schema models and relations.
services/workflows-service/src/collection-flow/** Added CollectionFlowEntityService and CollectionFlowEntityController with methods for creating, updating, and deleting entities.
services/workflows-service/src/end-user/** Updated EndUserCreateDto by adding optional properties country and additionalInfo.
services/workflows-service/src/document/** Introduced DocumentModule with DocumentControllerExternal, DocumentRepository, DocumentService and DTOs for document creation, update, deletion, and file re-upload.
services/workflows-service/src/providers/file/, services/workflows-service/src/storage/ Introduced new FileModule with FileService, FileRepository, StorageService and CustomerService; updated file handling and uploadNewFile method; storage module updated to resolve circular dependency via forwardRef.
services/workflows-service/src/business/** Removed documents field handling from business controller methods and streamlined the business entity creation flow.
services/workflows-service/src/workflow/** Updated WorkflowModule and WorkflowService to include FileModule and utilize FileService.

Sequence Diagram(s)

sequenceDiagram
  participant C as Client
  participant DC as DocumentControllerExternal
  participant DS as DocumentService
  participant DR as DocumentRepository
  participant FS as FileService
  participant DB as Database

  C->>DC: POST /document (with file and metadata)
  DC->>DS: createDocument(data, file)
  DS->>FS: uploadNewFile(projectId, workflowRuntimeData, file)
  FS-->>DS: File upload successful (URI, details)
  DS->>DR: create(document data + file details)
  DR-->>DS: Document record created
  DS-->>DC: Return created document info
  DC-->>C: Response with Document details
Loading

Possibly related PRs

  • Documents rework #3019: The changes in the main PR, which involve the addition of a new dependency and modifications to the document management schema, are related to the retrieved PR that focuses on a comprehensive rework of document handling, including the introduction of new tables and relationships for documents. Both PRs address aspects of document management within the application.
  • Format ongoing summary in the UI #3006: The changes in the main PR, which involve adding a new dependency to the package.json for the @ballerine/ui package, are related to the retrieved PR that updates the version of the @ballerine/ui dependency, indicating a direct connection at the code level.

Suggested reviewers

  • tomer-shvadron
  • alonp99

Poem

Hey, I'm a rabbit in a code-filled glen,
Hopping through changes again and again.
New hooks and modules, a bouncy delight,
Databases and files in our code take flight.
With carrots of code and joy so true,
I celebrate these changes just for you!
🐇✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.

Actionable comments posted: 11

🔭 Outside diff range comments (2)
packages/ui/src/components/organisms/Form/DynamicForm/_stories/FileUploadShowcase/FileUploadShowcase.tsx (1)

Line range hint 95-97: Add error handling to onSubmit.

The onSubmit callback should include error handling.

           onSubmit={() => {
-            console.log('onSubmit');
+            try {
+              console.log('onSubmit');
+              // Handle form submission
+            } catch (error) {
+              console.error('Form submission failed:', error);
+              // Show error to user
+            }
           }}
packages/ui/src/components/organisms/Form/DynamicForm/fields/FileField/FileField.tsx (1)

Breaking Change Confirmed: Requires Version Bump and Migration Guide

The change to make uploadSettings required is indeed breaking as:

  • DocumentField extends from FileField's interface
  • Package is published to npm (@ballerine/ui@0.5.72)
  • Tests verify undefined uploadSettings case

Recommendations:

  • Bump major version (0.6.0) since package is pre-1.0
  • Add migration guide in documentation
  • Consider maintaining backward compatibility through a deprecated optional pattern
🔗 Analysis chain

Line range hint 20-26: Breaking Change: uploadSettings is now required.

Making uploadSettings required is a breaking change that could affect existing implementations. Consider:

  1. Maintaining backward compatibility
  2. Adding migration guide
  3. Incrementing major version
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for existing usage of FileField to assess impact
rg -A 5 "uploadSettings[?]?:" .

Length of output: 10205


Script:

#!/bin/bash
# Search for FileField usage in actual implementations
rg "FileField" --type ts --type tsx -B 2 -A 2 . | grep -v "test\|story\|showcase"

# Check for any existing migration guides or documentation
fd -e md . | xargs rg -l "FileField|migration|breaking.change" 

Length of output: 174


Script:

#!/bin/bash
# Find all ts/tsx files and search for FileField
fd -e ts -e tsx . | xargs rg "FileField" -B 2 -A 2 | grep -v "test\|story\|showcase"

# Check package.json for version and publication status
cat packages/ui/package.json

# Look for documentation specifically in the ui package
fd -e md . packages/ui/ | xargs rg -l "FileField|migration|breaking.change"

Length of output: 15228

🧹 Nitpick comments (33)
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/EntityFields.tsx (4)

3-3: Consider renaming the type TDeepthLevelStack.
It appears there may be a minor spelling misalignment in the type name ("Deepth" vs. "Depth"). Renaming it to something like TDepthLevelStack will improve clarity.


44-48: Remove or clarify the commented-out code.
Lines 44–48 contain commented-out references to useHttp. If they’re no longer necessary, removing them will keep your codebase lean. Otherwise, add a brief explanation indicating why they remain commented out.


67-67: Fix spelling for "documentFieldDefinitons".
Minor improvement for readability:

- const documentFieldDefinitons =
+ const documentFieldDefinitions =

153-194: Refine the variable name for child elements.
Using childrens can be slightly confusing. Consider a more descriptive alternative like renderableChildren or lockedChildren to clearly convey its purpose.

packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/types/index.ts (1)

1-6: Double-underscore properties are slightly unconventional.
Properties like __id, __isLocked, and __isCreated work, but they may be less intuitive for some team members. Consider a naming approach that conveys the meaning without underscores if you don’t have a specific convention enforcing them.

packages/ui/src/common/hooks/useHttp/types.ts (1)

1-6: Consider enhancing the HTTP parameters interface.

  1. The purpose of the required resultPath property is unclear. Consider adding JSDoc comments to explain its usage.
  2. Consider adding common HTTP parameters:
    • params for query parameters
    • data or body for request payload
    • timeout for request timeout
    • withCredentials for CORS

Apply this diff to enhance the interface:

+/**
+ * Parameters for making HTTP requests
+ */
 export interface IHttpParams {
   url: string;
+  /**
+   * @description Path to extract the result from the response
+   */
   resultPath: string;
   headers?: Record<string, string>;
   method?: 'POST' | 'PUT' | 'GET' | 'DELETE';
+  params?: Record<string, string>;
+  data?: unknown;
+  timeout?: number;
+  withCredentials?: boolean;
 }
packages/ui/src/common/hooks/useHttp/utils/format-headers.ts (1)

3-15: Add validation and error handling for header formatting.

The current implementation lacks validation and error handling for header names and values.

Apply this diff to enhance the function:

+const isValidHeaderName = (name: string) => /^[a-zA-Z0-9-]+$/.test(name);
+const isValidHeaderValue = (value: string) => !value.includes('\n') && !value.includes('\r');
+
 export const formatHeaders = (
   headers: Record<string, string>,
   metadata: Record<string, string> = {},
 ) => {
   const formattedHeaders: Record<string, string> = {};
 
   Object.entries(headers).forEach(([key, value]) => {
+    if (!isValidHeaderName(key)) {
+      throw new Error(`Invalid header name: ${key}`);
+    }
+    if (!isValidHeaderValue(value)) {
+      throw new Error(`Invalid header value for ${key}`);
+    }
     const formattedValue = formatString(value, metadata);
+    if (!isValidHeaderValue(formattedValue)) {
+      throw new Error(`Invalid formatted header value for ${key}`);
+    }
     formattedHeaders[key] = formattedValue;
   });
 
   return formattedHeaders;
 };
packages/ui/src/common/hooks/useHttp/utils/request.ts (1)

24-28: Improve error handling.

The current error handling could be enhanced:

  1. Avoid logging raw errors as they might contain sensitive information.
  2. Consider transforming the error before re-throwing to ensure consistent error format.
   } catch (error) {
-    console.error('Failed to perform request.', error);
+    console.error('Failed to perform request.');
+    
+    // Transform error to ensure consistent format
+    if (axios.isAxiosError(error)) {
+      throw {
+        message: error.response?.data?.message || 'Request failed',
+        status: error.response?.status,
+        code: error.code
+      };
+    }

     throw error;
   }
packages/ui/src/common/hooks/useHttp/useHttp.ts (2)

20-22: Improve error type handling.

The current error type casting to Error might be unsafe. Consider using a more specific error type or proper type checking.

-        setResponseError(error as Error);
+        if (error instanceof Error) {
+          setResponseError(error);
+        } else {
+          setResponseError(new Error('An unknown error occurred'));
+        }

11-27: Add type information for response data.

Consider adding generic type parameters to improve type safety for the response data.

-  const runRequest = useCallback(
+  const runRequest = useCallback<(requestPayload?: any) => Promise<T | undefined>>(
     async (requestPayload?: any) => {
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/hooks/useChildrenDisabledOnLock/useChildrenDisabledOnLock.ts (1)

16-24: Replace hardcoded disable rule with a more meaningful condition.

The current disable rule {==: [1, 1]} is hardcoded to always be true. Consider using a more meaningful condition that reflects the actual locking state.

           disable: [
             ...(child.disable || []),
             {
               engine: 'json-logic' as const,
               value: {
-                '==': [1, 1],
+                var: 'isLocked'
               },
             },
           ],
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/hooks/useEntityLock/useEntityLock.ts (2)

22-22: Fix typo in comment.

The comment has a typo: "it it" should be "it".

-    // If the entity has an id, it it means its already created and should't be mutated.
+    // If the entity has an id, it means it's already created and shouldn't be mutated.

41-58: Optimize performance by avoiding the delete operator.

Using the delete operator can impact performance. Consider using an undefined assignment instead.

 const unlockEntity = useCallback(() => {
   const updatedArray = entities.map(entity => {
     if (entity.__id === entityId) {
       const newEntity = {
         ...entity,
       };

-      delete newEntity.__isLocked;
+      newEntity.__isLocked = undefined;
       onUnlock(newEntity);

       return newEntity;
     }

     return entity;
   });

   onChange(updatedArray);
 }, [onChange, entityId, entities, onUnlock]);
🧰 Tools
🪛 Biome (1.9.4)

[error] 48-48: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/hooks/useEntityFieldGroupList/useEntityFieldGroupList.ts (2)

51-57: Improve error handling.

The catch block should:

  1. Use a more specific error type
  2. Include error details in the toast message
  3. Consider retrying the operation
-        } catch (error) {
-          toast.error('Failed to delete entity.');
+        } catch (error: unknown) {
+          const errorMessage = error instanceof Error ? error.message : 'Unknown error';
+          toast.error(`Failed to delete entity: ${errorMessage}`);

36-38: Add type guard for value array.

The value array access needs better type safety.

-      if (!Array.isArray(value)) {
+      if (!Array.isArray(value) || !value.length) {
         return;
       }
packages/ui/src/components/organisms/Form/DynamicForm/fields/FileField/hooks/useFileUpload/useFileUpload.ts (1)

42-44: Refactor duplicate FormData creation.

The FormData creation logic is duplicated between 'change' and 'submit' modes.

Extract to a helper function:

+  const createFileFormData = useCallback((file: File) => {
+    const formData = new FormData();
+    formData.append('file', file);
+    return formData;
+  }, []);

-          const formData = new FormData();
-          formData.append('file', e.target?.files?.[0] as File);
+          const formData = createFileFormData(e.target?.files?.[0] as File);

Also applies to: 57-58

packages/ui/src/components/organisms/Form/DynamicForm/_stories/FileUploadShowcase/FileUploadShowcase.tsx (1)

17-19: Make upload URLs configurable.

The localhost URLs should be configurable to support different environments.

+const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:3000';
+
 const schema: Array<IFormElement<any, any>> = [
   {
     id: 'FileField:Regular',
     element: 'filefield',
     valueDestination: 'file-regular',
     params: {
       label: 'Regular Upload',
       placeholder: 'Select File',
       uploadSettings: {
-        url: 'http://localhost:3000/upload',
+        url: `${API_BASE_URL}/upload`,
         resultPath: 'filename',
         method: 'POST',
       },
     },
   },

Also applies to: 31-35, 49-51, 68-70

packages/ui/src/components/organisms/Form/Validator/validators/document/document-validator.unit.test.ts (2)

17-21: Add more specific error messages.

The error messages in the tests should be more descriptive to help identify the failure cause.

-      'Test message',
+      'Value must be an array of documents',
     );
   });

   it('should throw error when array is empty', () => {
     expect(() => documentValidator([], mockParams)).toThrow(
-      'Test message',
+      'Document array cannot be empty',
     );
   });

Also applies to: 23-25


51-61: Add edge case tests.

The test suite should include edge cases such as:

  • Documents with multiple pages
  • Invalid page numbers
  • Special characters in IDs

Would you like me to generate additional test cases for these scenarios?

packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/EntityFieldGroup.tsx (2)

20-26: Consider enhancing type safety for HTTP parameters.

The httpsParams property could benefit from more specific typing for request methods and URL patterns.

 export interface IEntityFieldGroupParams extends IFieldListParams {
   httpsParams: {
-    createEntity: IHttpParams;
-    deleteEntity: IHttpParams;
+    createEntity: IHttpParams & { method: 'POST' | 'PUT' };
+    deleteEntity: IHttpParams & { method: 'DELETE' };
   };
   lockText?: string;
 }

37-37: Consider memoizing callback functions.

The addItem and removeItem callbacks from useEntityFieldGroupList should be memoized to prevent unnecessary re-renders.

-  const { items, isRemovingEntity, addItem, removeItem } = useEntityFieldGroupList({ element });
+  const { items, isRemovingEntity, addItem: rawAddItem, removeItem: rawRemoveItem } = useEntityFieldGroupList({ element });
+  const addItem = useCallback(rawAddItem, [rawAddItem]);
+  const removeItem = useCallback(rawRemoveItem, [rawRemoveItem]);
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/hooks/useChildrenDisabledOnLock/useChildrenDisabledOnLock.unit.test.ts (1)

41-46: Consider using a constant for the disable rule.

The disable rule is duplicated across test cases and could be extracted to a constant.

+const LOCK_DISABLE_RULE = {
+  engine: 'json-logic',
+  value: {
+    '==': [1, 1],
+  },
+};

-    const expectedDisableRule = {
-      engine: 'json-logic',
-      value: {
-        '==': [1, 1],
-      },
-    };
+    const expectedDisableRule = LOCK_DISABLE_RULE;
packages/ui/src/common/hooks/useHttp/utils/request.unit.test.ts (1)

75-91: Add more specific error test cases.

The error handling test could be expanded to cover specific HTTP error codes and network errors.

+  it('should handle network errors', async () => {
+    const requestParams = {
+      url: 'http://api.example.com/test',
+      method: 'GET',
+      headers: {},
+    } as const;
+    const error = new Error('Network Error');
+    error.name = 'NetworkError';
+
+    mockFormatString.mockReturnValue('http://api.example.com/test');
+    mockFormatHeaders.mockReturnValue({});
+    mockAxios.mockRejectedValue(error);
+
+    const consoleSpy = vi.spyOn(console, 'error');
+
+    await expect(request(requestParams, {})).rejects.toThrow('Network Error');
+    expect(consoleSpy).toHaveBeenCalledWith('Failed to perform request.', error);
+  });
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/EntityFieldGroup.stories.tsx (1)

116-122: Enhance story documentation and add variants.

The story could benefit from better documentation and additional variants to showcase different states.

 export default {
   component: EntityFieldGroup,
+  parameters: {
+    docs: {
+      description: {
+        component: 'A dynamic form field group that manages a list of entity fields.',
+      },
+    },
+  },
 };

 export const Default = {
   render: () => <EntityFieldGroup />,
+  parameters: {
+    docs: {
+      description: {
+        story: 'Default state with pre-filled data.',
+      },
+    },
+  },
 };
+
+export const Empty = {
+  render: () => <EntityFieldGroup />,
+  args: {
+    initialContext: {},
+  },
+};
packages/ui/src/common/hooks/useHttp/useHttp.unit.test.ts (3)

92-109: Consider using a shorter timeout for testing loading state.

The test uses a 100ms timeout which might slow down the test suite unnecessarily. Consider using a shorter timeout (e.g., 10ms) since we're only testing state transitions.

 vi.mocked(request).mockImplementationOnce(
   () =>
     new Promise(resolve => {
-      setTimeout(() => resolve(mockResponse), 100);
+      setTimeout(() => resolve(mockResponse), 10);
     }),
 );

78-90: Enhance error test coverage.

The error test could be more thorough by:

  1. Testing different types of errors (network, validation, etc.)
  2. Verifying error message propagation
  3. Testing error state reset on subsequent successful requests

9-14: Consider using TypeScript for mock parameters.

The mockParams object could benefit from explicit type annotations to ensure type safety in tests.

-  const mockParams = {
+  const mockParams: IHttpParams = {
     url: 'test-url',
     resultPath: 'data.items',
     method: 'GET' as const,
     headers: {},
   };
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/hooks/useEntityFieldGroupList/useEntityFieldGroupList.unit.test.ts (2)

64-67: Avoid using 'any' type in mock implementation.

The cryptoSpy mock uses 'any' type which bypasses TypeScript's type checking. Consider using a more specific type.

-    cryptoSpy.mockReturnValue(mockUUID as any);
+    cryptoSpy.mockReturnValue(mockUUID);

95-107: Enhance error case test coverage.

The test for handling undefined value could be expanded to cover more edge cases:

  • null values
  • non-array objects
  • invalid array items
packages/ui/src/components/organisms/Form/DynamicForm/fields/FileField/FileField.tsx (1)

51-57: Optimize file creation for string values.

Creating an empty File object for string values is inefficient. Consider using a separate type or interface to represent file identifiers.

-    if (typeof value === 'string') {
-      return new File([], value);
-    }
+    if (typeof value === 'string') {
+      return { id: value, name: value } as FileIdentifier;
+    }
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFieldGroupDocument/EntityFieldGroupDocument.tsx (1)

67-71: Fix typo in warning message.

The warning message contains a typo: "migging" should be "missing".

-      console.warn('Template id is migging in element', element);
+      console.warn('Template id is missing in element', element);
packages/ui/src/components/organisms/Form/DynamicForm/fields/FileField/hooks/useFileUpload/useFileUpload.unit.test.ts (2)

100-114: Consider adding assertions for task properties.

While the test verifies basic task creation, it would be more robust to assert the specific properties of the added task.

   await result.current.handleChange(mockEvent);

   expect(mockOnChange).toHaveBeenCalledWith(mockFile);
-  expect(mockAddTask).toHaveBeenCalled();
+  expect(mockAddTask).toHaveBeenCalledWith(expect.objectContaining({
+    id: mockElement.id,
+    run: expect.any(Function)
+  }));

192-202: Consider adding more loading state scenarios.

While the basic loading state is tested, consider adding tests for:

  1. State transitions (idle → loading → complete)
  2. Multiple concurrent uploads
  3. Loading state during error scenarios
🛑 Comments failed to post (11)
packages/ui/src/components/organisms/Form/Validator/validators/document/document-validator.ts (1)

5-30: 🛠️ Refactor suggestion

Enhance error handling and validation in document validator.

The validator uses the same error message for different validation failures and lacks bounds checking.

Apply this diff to improve the validator:

 export const documentValidator: TValidator<
   TDocument[],
   IDocumentValidatorParams,
   TBaseValidators | 'document'
 > = (value, params) => {
-  const { message = 'Document is required' } = params;
+  const {
+    message = {
+      required: 'Document is required',
+      notFound: 'Document not found',
+      invalidPage: 'Invalid page number',
+      missingProperty: 'Required document property not found',
+    },
+  } = params;
   const { id, pageNumber = 0, pageProperty = 'ballerineFileId' } = params.value;
 
+  if (pageNumber < 0) {
+    throw new Error(message.invalidPage);
+  }
+
   if (!Array.isArray(value) || !value.length) {
-    throw new Error(message);
+    throw new Error(message.required);
   }
 
   const document = value.find(doc => doc.id === id);
 
   if (!document) {
-    throw new Error(message);
+    throw new Error(message.notFound);
   }
 
+  if (pageNumber >= document.pages.length) {
+    throw new Error(message.invalidPage);
+  }
+
   const documentValue = document.pages[pageNumber]?.[pageProperty];
 
   if (!documentValue) {
-    throw new Error(message);
+    throw new Error(message.missingProperty);
   }
 
   return true;
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

export const documentValidator: TValidator<
  TDocument[],
  IDocumentValidatorParams,
  TBaseValidators | 'document'
> = (value, params) => {
  const {
    message = {
      required: 'Document is required',
      notFound: 'Document not found',
      invalidPage: 'Invalid page number',
      missingProperty: 'Required document property not found',
    },
  } = params;
  const { id, pageNumber = 0, pageProperty = 'ballerineFileId' } = params.value;

  if (pageNumber < 0) {
    throw new Error(message.invalidPage);
  }

  if (!Array.isArray(value) || !value.length) {
    throw new Error(message.required);
  }

  const document = value.find(doc => doc.id === id);

  if (!document) {
    throw new Error(message.notFound);
  }

  if (pageNumber >= document.pages.length) {
    throw new Error(message.invalidPage);
  }

  const documentValue = document.pages[pageNumber]?.[pageProperty];

  if (!documentValue) {
    throw new Error(message.missingProperty);
  }

  return true;
};
packages/ui/src/common/hooks/useHttp/utils/request.ts (1)

7-7: ⚠️ Potential issue

Fix typo in type name.

The type name TReuqestParams contains a typo and should be TRequestParams.

-export type TReuqestParams = Omit<IHttpParams, 'resultPath'>;
+export type TRequestParams = Omit<IHttpParams, 'resultPath'>;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

export type TRequestParams = Omit<IHttpParams, 'resultPath'>;
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/hooks/useEntityFieldGroupList/useEntityFieldGroupList.ts (1)

42-43: ⚠️ Potential issue

Remove hardcoded condition.

The if (true) condition is replacing actual entity existence check logic. This could lead to unnecessary API calls and incorrect behavior.

-      // if (entity?.id) {
-      if (true) {
+      if (entity?.id) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      if (entity?.id) {
🧰 Tools
🪛 Biome (1.9.4)

[error] 43-43: Unexpected constant condition.

(lint/correctness/noConstantCondition)

packages/ui/src/components/organisms/Form/DynamicForm/fields/FileField/hooks/useFileUpload/useFileUpload.ts (1)

41-47: 🛠️ Refactor suggestion

Add file validation.

The hook should validate file existence and potentially file type before upload.

+      const file = e.target?.files?.[0];
+      if (!file) {
+        console.error('No file selected');
+        return;
+      }
+      
+      if (params.allowedTypes && !params.allowedTypes.includes(file.type)) {
+        console.error('Invalid file type');
+        return;
+      }

       if (uploadOn === 'change') {
         try {
-          const formData = new FormData();
-          formData.append('file', e.target?.files?.[0] as File);
+          const formData = createFileFormData(file);

Also applies to: 56-61

packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/EntityFieldGroup.tsx (1)

57-57: 🛠️ Refactor suggestion

Replace non-null assertions with safe alternatives.

Using non-null assertions (!) can lead to runtime errors if the assumptions are wrong.

-            entityId={entity.__id!}
+            entityId={entity.__id ?? throw new Error('Entity ID is required')}
-            onRemoveClick={() => removeItem(entity.__id!)}
+            onRemoveClick={() => entity.__id && removeItem(entity.__id)}

Also applies to: 61-61

packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/hooks/useEntityFieldGroupList/useEntityFieldGroupList.unit.test.ts (1)

57-74: 🛠️ Refactor suggestion

Use try-finally to ensure spy restoration.

The cryptoSpy should be restored even if the test fails. Wrap the test in a try-finally block.

   it('should add new item with generated __id', async () => {
     const mockOnChange = vi.fn();
     vi.mocked(useField).mockReturnValue({
       onChange: mockOnChange,
       value: [],
     } as unknown as ReturnType<typeof useField>);

     const mockUUID = '123-456';
     const cryptoSpy = vi.spyOn(crypto, 'randomUUID');
     cryptoSpy.mockReturnValue(mockUUID);
+    try {
       const { result } = renderHook(() => useEntityFieldGroupList({ element: mockElement }));
       await result.current.addItem();
       expect(mockOnChange).toHaveBeenCalledWith([{ __id: mockUUID }]);
+    } finally {
       cryptoSpy.mockRestore();
+    }
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  it('should add new item with generated __id', async () => {
    const mockOnChange = vi.fn();
    vi.mocked(useField).mockReturnValue({
      onChange: mockOnChange,
      value: [],
    } as unknown as ReturnType<typeof useField>);

    const mockUUID = '123-456';
    const cryptoSpy = vi.spyOn(crypto, 'randomUUID');
    cryptoSpy.mockReturnValue(mockUUID);
    try {
      const { result } = renderHook(() => useEntityFieldGroupList({ element: mockElement }));
      await result.current.addItem();
      expect(mockOnChange).toHaveBeenCalledWith([{ __id: mockUUID }]);
    } finally {
      cryptoSpy.mockRestore();
    }
  });
packages/ui/src/components/organisms/Form/DynamicForm/fields/FileField/FileField.tsx (1)

35-37: ⚠️ Potential issue

Replace non-null assertion with runtime check.

Using non-null assertion (!) is risky as it bypasses TypeScript's null checks. Consider adding a runtime check instead.

-  const { handleChange, isUploading: disabledWhileUploading } = useFileUpload(
-    element,
-    element.params!,
-  );
+  if (!element.params) {
+    throw new Error('FileField requires params to be defined');
+  }
+  const { handleChange, isUploading: disabledWhileUploading } = useFileUpload(
+    element,
+    element.params,
+  );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    if (!element.params) {
      throw new Error('FileField requires params to be defined');
    }
    const { handleChange, isUploading: disabledWhileUploading } = useFileUpload(
      element,
      element.params,
    );
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFieldGroupDocument/EntityFieldGroupDocument.tsx (2)

73-76: ⚠️ Potential issue

Avoid type assertion for template ID.

Using type assertion for the template ID is risky. Consider adding runtime type checking.

+    const templateId = element.params?.template?.id;
+    if (typeof templateId !== 'string') {
+      console.warn('Invalid template ID type', templateId);
+      return;
+    }
     const updatedDocuments = removeDocumentFromListByTemplateId(
       documentsList,
-      element.params?.template?.id as string,
+      templateId,
     );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    const templateId = element.params?.template?.id;
    if (typeof templateId !== 'string') {
      console.warn('Invalid template ID type', templateId);
      return;
    }
    const updatedDocuments = removeDocumentFromListByTemplateId(
      documentsList,
      templateId,
    );

85-96: ⚠️ Potential issue

Add type safety for file handling.

The file type assertion could lead to runtime errors. Consider adding validation and proper error handling.

     (e: React.ChangeEvent<HTMLInputElement>) => {
       const documents = get(documentsList || [], element.valueDestination);
+      const file = e.target.files?.[0];
+      if (!file) {
+        console.warn('No file selected');
+        return;
+      }
       const updatedDocuments = createOrUpdateFileIdOrFileInDocuments(
         documents,
         element,
-        e.target.files?.[0] as File,
+        file,
       );
       onChange(updatedDocuments);
     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  const handleChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      const documents = get(documentsList || [], element.valueDestination);
      const file = e.target.files?.[0];
      if (!file) {
        console.warn('No file selected');
        return;
      }
      const updatedDocuments = createOrUpdateFileIdOrFileInDocuments(
        documents,
        element,
        file,
      );
      onChange(updatedDocuments);
    },
    [onChange],
  );
packages/ui/src/components/organisms/Form/DynamicForm/fields/FileField/hooks/useFileUpload/useFileUpload.unit.test.ts (2)

140-182: 🛠️ Refactor suggestion

Standardize error handling across upload modes.

The error handling differs between immediate and submit-time uploads. Consider standardizing the approach:

  1. Use error callbacks instead of console.error
  2. Ensure consistent error object structure
  3. Add type safety for error objects
-    expect(consoleSpy).toHaveBeenCalledWith('Failed to upload file.', expect.any(Error));
+    expect(mockOnChange).toHaveBeenCalledWith(
+      expect.objectContaining({
+        error: expect.objectContaining({
+          message: 'Upload failed'
+        })
+      })
+    );

Committable suggestion skipped: line range outside the PR's diff.


117-138: 🛠️ Refactor suggestion

Consider enhancing error handling.

Using console.log for error reporting is not ideal for production code. Consider:

  1. Throwing a specific error type
  2. Using a proper error handling mechanism
  3. Implementing error callbacks
-    expect(consoleSpy).toHaveBeenCalledWith('Failed to upload, no upload settings provided');
+    expect(mockOnChange).toHaveBeenCalledWith(
+      expect.objectContaining({
+        error: new Error('No upload settings provided')
+      })
+    );

Committable suggestion skipped: line range outside the PR's diff.

describe('formatHeaders', () => {
it('should format headers with metadata', () => {
const headers = {
Authorization: 'Bearer {token}',

Check failure

Code scanning / CodeQL

Hard-coded credentials Critical

The hard-coded value "Bearer {token}" is used as
authorization header
.
const result = formatHeaders(headers, metadata);

expect(result).toEqual({
Authorization: 'Bearer abc123',

Check failure

Code scanning / CodeQL

Hard-coded credentials Critical

The hard-coded value "Bearer abc123" is used as
authorization header
.
url: 'http://api.example.com/{path}',
method: 'GET',
headers: {
Authorization: 'Bearer {token}',

Check failure

Code scanning / CodeQL

Hard-coded credentials Critical

The hard-coded value "Bearer {token}" is used as
authorization header
.
const result = await request(requestParams, metadata);

expect(mockFormatString).toHaveBeenCalledWith('http://api.example.com/{path}', metadata);
expect(mockFormatHeaders).toHaveBeenCalledWith({ Authorization: 'Bearer {token}' }, metadata);

Check failure

Code scanning / CodeQL

Hard-coded credentials Critical

The hard-coded value "Bearer {token}" is used as
authorization header
.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (14)
packages/ui/src/components/organisms/Form/Validator/validators/document/document-validator.unit.test.ts (4)

8-15: Consider improving type safety of mock data.

While the mock data structure is appropriate, consider removing the type assertion and explicitly defining the mock data with proper types.

-  const mockParams = {
+  const mockParams: ICommonValidator<IDocumentValidatorParams, TBaseValidators | 'document'> = {
     message: 'Test message',
     value: {
       id: 'test-id',
       pageNumber: 0,
       pageProperty: 'ballerineFileId',
     },
-  } as ICommonValidator<IDocumentValidatorParams, TBaseValidators | 'document'>;
+  };

17-31: Improve type safety in test data and add boundary tests.

The test cases for invalid input could be improved:

  1. Remove type assertions and use proper typing:
-    expect(() => documentValidator(null as unknown as TDocument[], mockParams)).toThrow(
+    expect(() => documentValidator(null as any, mockParams)).toThrow(
  1. Add boundary tests for array size limits:
it('should handle large document arrays', () => {
  const largeDocArray = Array(1000).fill({
    id: 'test-id',
    pages: [{ ballerineFileId: 'valid-file-id' }],
    propertiesSchema: {},
  });
  expect(documentValidator(largeDocArray, mockParams)).toBe(true);
});

33-49: Make test descriptions more specific.

The test descriptions could be more descriptive about the exact scenario being tested.

-  it('should throw error when document page does not exist', () => {
+  it('should throw error when document has empty pages array', () => {

-  it('should throw error when document page property does not exist', () => {
+  it('should throw error when ballerineFileId is missing from document page', () => {

51-80: Add test cases for edge cases and error messages.

The valid document and default parameter tests look good, but consider adding:

  1. Test for exact error message content
  2. Test for multiple pages in a document
  3. Test for multiple documents in the array

Example:

it('should handle multiple pages in a document', () => {
  const mockDocuments = [{
    id: 'test-id',
    pages: [
      { ballerineFileId: 'file-1' },
      { ballerineFileId: 'file-2' }
    ],
    propertiesSchema: {},
  }] as TDocument[];
  
  expect(documentValidator(mockDocuments, {
    ...mockParams,
    value: { ...mockParams.value, pageNumber: 1 }
  })).toBe(true);
});
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/hooks/useEntityLock/useEntityLock.ts (3)

7-14: Consider adding TypeScript types for callback parameters.

The onLock and onUnlock callback parameters would benefit from explicit TypeScript types to ensure type safety across component boundaries.

-  onLock: (entity: IEntity) => void,
-  onUnlock: (entity: IEntity) => void,
+  onLock: (entity: Readonly<IEntity>) => void,
+  onUnlock: (entity: Readonly<IEntity>) => void,

21-24: Improve comment clarity.

The comment appears to have a typo and could be more descriptive about the locking behavior.

-    // If the entity has an id, it it means its already created and should't be mutated.
+    // Check if the entity is locked to prevent mutations

41-58: Optimize performance by avoiding object spread in map.

The unlockEntity function creates a new object for each entity in the array, even when not needed.

   const unlockEntity = useCallback(() => {
+    if (!entities?.length) return;
     const updatedArray = entities.map(entity => {
       if (entity.__id === entityId) {
-        const newEntity = {
-          ...entity,
-        };
-        delete newEntity.__isLocked;
+        const { __isLocked, ...newEntity } = entity;
         onUnlock(newEntity);
         return newEntity;
       }
       return entity;
     });
🧰 Tools
🪛 Biome (1.9.4)

[error] 48-48: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/EntityFieldGroup.tsx (1)

70-74: Add ARIA label for better accessibility.

The add button should have an ARIA label for better screen reader support.

-        <Button onClick={addItem} disabled={disabled}>
+        <Button 
+          onClick={addItem} 
+          disabled={disabled}
+          aria-label={`Add ${addButtonLabel}`}>
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/EntityFieldGroup.stories.tsx (1)

106-106: Remove commented-out code.

The commented-out onEvent prop should be removed.

-          // onEvent={console.log}
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFieldGroupDocument/EntityFieldGroupDocument.tsx (3)

67-69: Fix typo in console warning message.

The warning message contains a typo in the word "missing".

-      console.warn('Template id is migging in element', element);
+      console.warn('Template id is missing in element', element);

112-112: Fix typo in UI text.

The text "No File Choosen" contains a spelling error.

-        <span className="truncate text-sm">{file ? file.name : 'No File Choosen'}</span>
+        <span className="truncate text-sm">{file ? file.name : 'No File Chosen'}</span>

98-145: Add keyboard navigation support.

The component should support keyboard navigation for better accessibility.

Add keyboard event handlers to support Enter and Space key interactions:

       <div
         className={ctw(
           'relative flex h-[56px] flex-row items-center gap-3 rounded-[16px] border bg-white px-4',
           { 'pointer-events-none opacity-50': disabled },
         )}
         onClick={focusInputOnContainerClick}
+        onKeyDown={(e) => {
+          if (e.key === 'Enter' || e.key === ' ') {
+            e.preventDefault();
+            focusInputOnContainerClick();
+          }
+        }}
+        role="button"
+        tabIndex={0}
         data-testid={createTestId(element, stack)}
       >
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/EntityFields.tsx (2)

90-90: Replace hardcoded delay with configuration.

The hardcoded delay should be moved to a configuration or environment variable for better flexibility.

-await delay(1000);
+await delay(config.entityCreationDelay);

113-118: Enhance error handling with specific error types.

The current error handling is too generic. Consider handling specific error types and providing more detailed error messages.

-} catch (error) {
-  toast.error('Failed to create entity.');
-  console.error(error);
-
-  return context;
+} catch (error) {
+  console.error('Entity creation failed:', error);
+  if (error instanceof NetworkError) {
+    toast.error('Network error while creating entity. Please try again.');
+  } else if (error instanceof ValidationError) {
+    toast.error('Invalid entity data. Please check your input.');
+  } else {
+    toast.error('Failed to create entity. Please try again later.');
+  }
+  return context;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9656b02 and d7a5d7a.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (17)
  • packages/ui/package.json (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/EntityFieldGroup.stories.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/EntityFieldGroup.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFieldGroupDocument/EntityFieldGroupDocument.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFieldGroupDocument/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/EntityFields.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/hooks/useEntityLock/useEntityLock.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/hooks/useEntityFieldGroupList/useEntityFieldGroupList.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/types/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/utils/delay.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/providers/TaskRunner/TaskRunner.tsx (2 hunks)
  • packages/ui/src/components/organisms/Form/Validator/types/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/document/document-validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/document/document-validator.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/document/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/document/types.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/index.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/utils/delay.ts
  • packages/ui/src/components/organisms/Form/Validator/validators/index.ts
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/types/index.ts
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFieldGroupDocument/index.ts
  • packages/ui/package.json
  • packages/ui/src/components/organisms/Form/Validator/validators/document/index.ts
  • packages/ui/src/components/organisms/Form/Validator/validators/document/types.ts
  • packages/ui/src/components/organisms/Form/DynamicForm/providers/TaskRunner/TaskRunner.tsx
  • packages/ui/src/components/organisms/Form/Validator/types/index.ts
  • packages/ui/src/components/organisms/Form/Validator/validators/document/document-validator.ts
🧰 Additional context used
🪛 Biome (1.9.4)
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/hooks/useEntityLock/useEntityLock.ts

[error] 48-48: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/hooks/useEntityFieldGroupList/useEntityFieldGroupList.ts

[error] 43-43: Unexpected constant condition.

(lint/correctness/noConstantCondition)

🔇 Additional comments (5)
packages/ui/src/components/organisms/Form/Validator/validators/document/document-validator.unit.test.ts (2)

1-6: LGTM! Clean and well-organized imports.

All necessary dependencies and types are properly imported.


1-80: Overall implementation is solid and well-tested.

The test suite provides good coverage of the document validator functionality with clear test cases and consistent structure. While there are some opportunities for improvement in type safety and edge case coverage, the current implementation is robust and maintainable.

packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/EntityFields.tsx (3)

1-30: LGTM! Well-structured imports and interface definition.

The imports are well-organized, and the interface is comprehensive with proper type definitions.


44-48: Verify the status of commented HTTP integration code.

The commented-out HTTP hook suggests incomplete implementation. Please clarify if this is intended to be implemented in a future update.

Would you like me to help implement the HTTP integration using the commented code as a reference?


153-194: LGTM! Well-implemented UI with proper loading states and test coverage.

The UI implementation follows React best practices with:

  • Proper conditional rendering
  • Well-structured component hierarchy
  • Good test coverage with data-testid attributes

Comment on lines 26 to 39
const lockEntity = useCallback(() => {
const updatedArray = entities.map(entity => {
if (entity.__id === entityId) {
const lockedEntity = { ...entity, __isLocked: true };
onLock(lockedEntity);

return lockedEntity;
}

return entity;
});

onChange(updatedArray);
}, [onChange, entityId, entities, onLock]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding error handling for undefined entities.

The lockEntity function should handle cases where the entity array is undefined.

   const lockEntity = useCallback(() => {
+    if (!entities?.length) return;
     const updatedArray = entities.map(entity => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const lockEntity = useCallback(() => {
const updatedArray = entities.map(entity => {
if (entity.__id === entityId) {
const lockedEntity = { ...entity, __isLocked: true };
onLock(lockedEntity);
return lockedEntity;
}
return entity;
});
onChange(updatedArray);
}, [onChange, entityId, entities, onLock]);
const lockEntity = useCallback(() => {
if (!entities?.length) return;
const updatedArray = entities.map(entity => {
if (entity.__id === entityId) {
const lockedEntity = { ...entity, __isLocked: true };
onLock(lockedEntity);
return lockedEntity;
}
return entity;
});
onChange(updatedArray);
}, [onChange, entityId, entities, onLock]);

Comment on lines 20 to 23
// const { run: deleteEntity, isLoading } = useHttp(
// element.params!.httpsParams?.deleteEntity,
// metadata,
// );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove commented-out code.

The commented HTTP deletion code should either be implemented or removed to maintain code cleanliness.

Comment on lines 42 to 43
// if (entity?.id) {
if (true) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix constant condition in if statement.

The condition if (true) appears to be a placeholder and should be replaced with actual logic.

-      // if (entity?.id) {
-      if (true) {
+      if (entity?.id) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// if (entity?.id) {
if (true) {
if (entity?.id) {
🧰 Tools
🪛 Biome (1.9.4)

[error] 43-43: Unexpected constant condition.

(lint/correctness/noConstantCondition)

Comment on lines 48 to 50
setIsRemovingEntity(true);
await delay(1000);
setIsRemovingEntity(false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove artificial delay in entity removal.

The artificial delay using delay(1000) should be replaced with actual business logic.

-          setIsRemovingEntity(true);
-          await delay(1000);
-          setIsRemovingEntity(false);
+          // Implement actual entity removal logic here

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +56 to +57
key={entity.id}
entityId={entity.__id!}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix inconsistent key usage.

The component uses entity.id as key but entity.__id for other operations. This could lead to rendering issues.

-            key={entity.id}
+            key={entity.__id!}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
key={entity.id}
entityId={entity.__id!}
key={entity.__id!}
entityId={entity.__id!}

Comment on lines +102 to +104
onSubmit={() => {
console.log('onSubmit');
}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove console.log from onSubmit handler.

Production code should not contain console.log statements.

-          onSubmit={() => {
-            console.log('onSubmit');
-          }}
+          onSubmit={() => {
+            // Handle form submission
+          }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onSubmit={() => {
console.log('onSubmit');
}}
onSubmit={() => {
// Handle form submission
}}

if (entity.__id === lockedEntity.__id) {
const newEntity = {
...entity,
id: `${entity.__id}-${crypto.randomUUID()}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add browser compatibility check for crypto.randomUUID().

The direct usage of crypto.randomUUID() might not be supported in all browsers. Consider adding a fallback.

-id: `${entity.__id}-${crypto.randomUUID()}`,
+id: `${entity.__id}-${crypto.randomUUID?.() || Math.random().toString(36).substr(2, 9)}`,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
id: `${entity.__id}-${crypto.randomUUID()}`,
id: `${entity.__id}-${crypto.randomUUID?.() || Math.random().toString(36).substr(2, 9)}`,

setResponseError(null);

try {
const response = await request(params, metadata, requestPayload);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a default timeout on this request function?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, good point to add.

vi.mocked(request).mockImplementationOnce(
() =>
new Promise(resolve => {
setTimeout(() => resolve(mockResponse), 100);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a sleep utility in common by the way, I've seen this inlined or with a different name in some places.


const clearFileAndInput = useCallback(() => {
if (!element.params?.template?.id) {
console.warn('Template id is migging in element', element);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
console.warn('Template id is migging in element', element);
console.warn('Template id is missing in element', element);

Comment on lines 44 to 48
// const { metadata } = useDynamicForm();
// const { run: createEntity, isLoading: isCreatingEntity } = useHttp(
// element.params!.httpsParams?.createEntity,
// metadata,
// );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason not to remove this block?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was placeholder for your part.

Comment on lines +18 to +22
{
engine: 'json-logic' as const,
value: {
'==': [1, 1],
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to force disable all subitems of element i conditionally override schema rules with this one. This rule always returns true so i can disable everything on demand without any schema modifications.

// const entity = value.find(entity => entity.__id === id);

// if (entity?.id) {
if (true) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intentional?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parts similar to this are part of the integration scope?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, placholder.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have this utility in multiple places, including sleep from common.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Templolary, will be removed after implementation of real code.

const mockElementWithoutSettings = {
...mockElement,
params: {
uploadSettings: undefined as any,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using ts-expect-error for such cases.

throw new Error(message);
}

const documentValue = document.pages[pageNumber]?.[pageProperty];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pages has been reworked, we'll need to see what to do about this.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/EntityFieldGroup.tsx (1)

73-74: ⚠️ Potential issue

Fix inconsistent key usage.

The component uses entity.id as key but entity.__id for other operations. This could lead to rendering issues.

-            key={entity.id}
+            key={entity.__id!}
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFieldGroupDocument/EntityFieldGroupDocument.tsx (1)

87-87: ⚠️ Potential issue

Fix typo in warning message.

The word "migging" should be "missing". Also, follow the suggested format from the past review.

-      console.warn('Template id is migging in element', element);
+      console.warn('Template id is missing in element', element);
🧹 Nitpick comments (6)
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldGroupTypeProvider/types.ts (1)

3-5: LGTM! Consider adding JSDoc comments.

The interface is well-defined. Consider adding JSDoc comments to document the purpose of the interface and its property.

+/**
+ * Context interface for EntityFieldGroupType provider
+ */
 export interface IEntityFieldGroupTypeContext {
+  /** The type of entity field group, if defined */
   entityFieldGroupType?: TEntityFieldGroupType;
 }
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldGroupTypeProvider/hooks/external/useEntityFieldGroupType/useEntityFieldGroupType.ts (1)

4-12: LGTM! Consider enhancing the error message.

The hook implementation follows React best practices with proper error handling.

Consider making the error message more descriptive:

-    throw new Error('useEntityFieldGroupType must be used within a EntityFieldGroupTypeProvider');
+    throw new Error('useEntityFieldGroupType must be used within an EntityFieldGroupTypeProvider component');
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldGroupTypeProvider/EntityFieldGroupTypeProvider.tsx (1)

5-8: LGTM! Consider adding JSDoc comments for props interface.

The props interface is well-defined.

+/**
+ * Props for EntityFieldGroupTypeProvider component
+ */
 interface IEntityFieldGroupTypeProviderProps {
+  /** Child components to be wrapped by the provider */
   children: React.ReactNode;
+  /** Optional type of the entity field group */
   entityFieldGroupType?: TEntityFieldGroupType;
 }
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFieldGroupDocument/helpers/get-entity-field-group-document-value-destination.ts (1)

3-14: Document the $0 placeholder usage.

The paths contain a $0 placeholder which should be documented for clarity. Also, consider making the error message consistent with getEntityGroupValueDestination.

Apply these changes:

+// The $0 placeholder in the paths represents the index of the entity in the array
 export const getEntityFieldGroupDocumentValueDestination = (type: TEntityFieldGroupType) => {
   const valueDestinationsMap: Record<TEntityFieldGroupType, string> = {
     director: 'entity.data.additionalInfo.directors[$0].additionalInfo.documents',
     ubo: 'entity.data.additionalInfo.ubos[$0].documents',
   };

   if (!valueDestinationsMap[type]) {
-    throw new Error(`Invalid entity field group type in documentfield: ${type}`);
+    throw new Error(`Invalid entity group type: ${type}`);
   }

   return valueDestinationsMap[type];
 };
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/EntityFieldGroup.tsx (1)

95-95: Consider managing Toaster cleanup.

The Toaster component is rendered but there's no cleanup logic. Consider wrapping it in a separate component that handles cleanup on unmount.

-        <Toaster />
+        <ToasterWithCleanup />

Create a new component:

const ToasterWithCleanup = () => {
  useEffect(() => {
    return () => {
      // Clean up toasts on unmount
      toast.dismiss();
    };
  }, []);

  return <Toaster />;
};
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/EntityFieldGroup.stories.tsx (1)

107-107: Remove commented out code.

Remove commented out code that is not being used.

-          // onEvent={console.log}

Also applies to: 220-220, 329-329

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d7a5d7a and 53fa207.

📒 Files selected for processing (12)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/EntityFieldGroup.stories.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/EntityFieldGroup.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFieldGroupDocument/EntityFieldGroupDocument.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFieldGroupDocument/helpers/get-entity-field-group-document-value-destination.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/helpers/get-entity-group-value-destination.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/helpers/get-entity-group-value-destination.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldGroupTypeProvider/EntityFieldGroupTypeProvider.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldGroupTypeProvider/entity-field-group-type.context.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldGroupTypeProvider/hooks/external/useEntityFieldGroupType/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldGroupTypeProvider/hooks/external/useEntityFieldGroupType/useEntityFieldGroupType.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldGroupTypeProvider/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldGroupTypeProvider/types.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldGroupTypeProvider/hooks/external/useEntityFieldGroupType/index.ts
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (8)
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldGroupTypeProvider/index.ts (1)

1-2: LGTM! Clean and well-structured exports.

The barrel file follows best practices by:

  • Clearly exposing the provider component
  • Making the hook easily accessible
  • Maintaining a clean separation between provider and hook exports
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldGroupTypeProvider/entity-field-group-type.context.ts (1)

1-4: LGTM! Context initialization follows React best practices.

The context is properly initialized with TypeScript type safety.

packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldGroupTypeProvider/EntityFieldGroupTypeProvider.tsx (1)

10-26: LGTM! Component implementation follows React best practices.

The provider component is well-implemented with proper performance optimization using useMemo.

packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/helpers/get-entity-group-value-destination.ts (1)

3-16: LGTM! Well-structured helper function.

The implementation is type-safe, includes proper error handling, and follows a clean pattern for mapping entity types to their destinations.

packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/helpers/get-entity-group-value-destination.unit.test.ts (1)

5-39: LGTM! Comprehensive test coverage.

The tests are well-structured using the AAA pattern and cover all scenarios including error cases.

packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/EntityFieldGroup.tsx (1)

32-99: LGTM! Well-structured component.

The component is well-organized with proper type definitions, error handling, and separation of concerns using custom hooks.

packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFieldGroupDocument/EntityFieldGroupDocument.tsx (1)

68-78: Improve file object creation.

Creating an empty File object with no data but using the string as the name might lead to issues. Consider using a more appropriate representation for file-like objects or add proper validation.

Please verify:

  1. Is the string value expected to be a file ID or URL?
  2. Should we handle these cases differently?

Consider using a custom type to distinguish between file IDs and actual File objects:

type FileOrId = File | { id: string; name: string };
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/EntityFieldGroup.stories.tsx (1)

103-105: Remove console.log statements from onSubmit handlers.

Production code should not contain console.log statements.

Also applies to: 216-218, 325-327

Comment on lines 104 to 115
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const documents = get(documentsList || [], element.valueDestination);
const updatedDocuments = createOrUpdateFileIdOrFileInDocuments(
documents,
element,
e.target.files?.[0] as File,
);
onChange(updatedDocuments);
},
[onChange],
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for file selection.

The function assumes a file will always be present. Add proper error handling for cases where no file is selected.

   const handleChange = useCallback(
     (e: React.ChangeEvent<HTMLInputElement>) => {
+      if (!e.target.files?.length) {
+        return;
+      }
       const documents = get(documentsList || [], element.valueDestination);
       const updatedDocuments = createOrUpdateFileIdOrFileInDocuments(
         documents,
         element,
         e.target.files?.[0] as File,
       );
       onChange(updatedDocuments);
     },
     [onChange],
   );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const documents = get(documentsList || [], element.valueDestination);
const updatedDocuments = createOrUpdateFileIdOrFileInDocuments(
documents,
element,
e.target.files?.[0] as File,
);
onChange(updatedDocuments);
},
[onChange],
);
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
if (!e.target.files?.length) {
return;
}
const documents = get(documentsList || [], element.valueDestination);
const updatedDocuments = createOrUpdateFileIdOrFileInDocuments(
documents,
element,
e.target.files?.[0] as File,
);
onChange(updatedDocuments);
},
[onChange],
);

Comment on lines +12 to +84
const defaultSchema: Array<IFormElement<any, any>> = [
{
id: 'directors',
element: 'entityfieldgroup',
valueDestination: 'users',
params: {
label: 'Field List',
description: 'A list of repeatable form fields that can be added or removed',
defaultValue: `{
"firstName": firstName,
"lastName": lastName
}`,
type: 'director',
},
children: [
{
id: 'user-name',
element: 'textfield',
valueDestination: 'users[$0].firstName',
params: {
label: 'Text Field',
placeholder: 'Enter text',
description: 'Enter text for this list item',
},
validate: [
{
type: 'required',
value: {},
message: 'Name is required',
},
],
},
{
id: 'user-lastname',
element: 'textfield',
valueDestination: 'users[$0].lastName',
params: {
label: 'Last Name',
placeholder: 'Enter last name',
description: 'Enter your last name',
},
validate: [
{
type: 'required',
value: {},
message: 'Last name is required',
},
],
},
{
id: 'document',
element: 'documentfield',
valueDestination: 'users[$0].documents',
params: {
label: 'Document',
template: {
id: 'document',
},
},
validate: [
{
type: 'document',
value: {
id: 'document',
},
message: 'Document is required',
considerRequired: true,
},
],
},
],
},
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Reduce code duplication in schema definitions.

The schema definitions for default, ubos, and directors are nearly identical, differing only in the type parameter. Consider extracting the common schema into a factory function.

const createSchema = (type: 'director' | 'ubo') => {
  return [
    {
      id: 'directors',
      element: 'entityfieldgroup',
      valueDestination: 'users',
      params: {
        label: 'Field List',
        description: 'A list of repeatable form fields that can be added or removed',
        defaultValue: `{
          "firstName": firstName,
          "lastName": lastName
        }`,
        type,
      },
      children: [
        // ... rest of the schema
      ],
    },
    // ... submit button
  ];
};

const defaultSchema = createSchema('director');
const ubosSchema = createSchema('ubo');
const directorsSchema = createSchema('director');

Also applies to: 125-205, 234-314

* feat: added entity creation & deletion endpoints

* fix: fixed entity deletion

* feat: finalized creation & deletion logic of entities

* fix: lock fix
url: '{apiUrl}collection-flow/entity',
method: 'POST',
headers: {
Authorization: 'Bearer {token}',

Check failure

Code scanning / CodeQL

Hard-coded credentials Critical

The hard-coded value "Bearer {token}" is used as
authorization header
.
url: '{apiUrl}collection-flow/entity/{entityId}',
method: 'DELETE',
headers: {
Authorization: 'Bearer {token}',

Check failure

Code scanning / CodeQL

Hard-coded credentials Critical

The hard-coded value "Bearer {token}" is used as
authorization header
.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🔭 Outside diff range comments (1)
services/workflows-service/src/collection-flow/collection-flow.module.ts (1)

79-81: Remove duplicate provider declarations.

The following providers are declared twice in the providers array:

  • StorageService (lines 79 and 91)
  • FileRepository (lines 80 and 92)

Apply this diff to remove the duplicates:

    EntityRepository,
    StorageService,
    FileRepository,
    HookCallbackHandlerService,
    FileService,
    WorkflowEventEmitterService,
    FilterRepository,
    FilterService,
    UserService,
    UserRepository,
    PasswordService,
    CustomerService,
    CustomerRepository,
-   StorageService,
-   FileRepository,
    SalesforceService,
    SalesforceIntegrationRepository,
    SentryService,

Also applies to: 91-92

♻️ Duplicate comments (2)
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/EntityFieldGroup.stories.tsx (2)

103-105: ⚠️ Potential issue

Remove console.log statements from onSubmit handlers.

Production code should not contain console.log statements.

Apply this diff to all onSubmit handlers:

-          onSubmit={() => {
-            console.log('onSubmit');
-          }}
+          onSubmit={() => {
+            // Handle form submission
+          }}

Also applies to: 436-438, 553-555


12-92: 🛠️ Refactor suggestion

Reduce code duplication in schema definitions.

The schema definitions for default, ubos, and directors are nearly identical, differing only in the type parameter and some field configurations.

Follow the previous suggestion to extract common schema into a factory function.

Also applies to: 125-407, 462-542

🧹 Nitpick comments (9)
services/workflows-service/src/end-user/dtos/end-user-create.ts (2)

46-51: Add country code validation.

Consider using ISO country codes and adding validation to ensure the country field follows a standard format.

Apply this diff to add country code validation:

   @IsOptional()
   @ApiProperty({
     type: String,
+    description: 'ISO 3166-1 alpha-2 country code',
+    example: 'US',
   })
   @IsString()
+  @Matches(/^[A-Z]{2}$/, {
+    message: 'Country must be a valid ISO 3166-1 alpha-2 code',
+  })
   country?: string;

67-69: Add API documentation and validation for additionalInfo.

The property should have:

  1. API documentation for better OpenAPI specification.
  2. Validation for the object structure and its values.

Apply this diff to add documentation and validation:

   @IsOptional()
+  @ApiProperty({
+    type: 'object',
+    description: 'Additional information about the end user',
+    example: { customField1: 'value1', customField2: 'value2' },
+  })
   @IsObject()
+  @ValidateNested()
   additionalInfo?: Record<string, any>;
services/workflows-service/src/collection-flow/collection-flow-entity.service.ts (2)

17-54: Improve error handling and add logging.

Consider:

  1. Making the error message more descriptive.
  2. Adding logging for debugging purposes.
  3. Adding error handling for database operations.

Apply this diff to improve the implementation:

+import { Logger } from '@nestjs/common';
+
 @Injectable()
 export class CollectionFlowEntityService {
+  private readonly logger = new Logger(CollectionFlowEntityService.name);
+
   async createEntity(
     workflowId: string,
     entityType: BusinessPosition,
     entity: EntityCreateDto,
     projectId: TProjectId,
   ) {
+    this.logger.debug(
+      `Creating entity of type ${entityType} for workflow ${workflowId}`,
+    );
+
     return await this.prismaService.$transaction(async transaction => {
       const workflowRuntimeData =
         await this.workflowService.getWorkflowRuntimeDataByIdAndLockUnscoped({
           id: workflowId,
           transaction,
         });
 
       if (!workflowRuntimeData.businessId) {
         throw new BadRequestException(
-          `Attempted to create a Entity to a parent workflow without a business`,
+          `Cannot create entity: workflow ${workflowId} is not associated with a business`,
         );
       }
 
-      const endUser = await this.endUserService.create({
-        data: {
-          ...entity,
-          projectId,
-        },
-      });
+      try {
+        const endUser = await this.endUserService.create({
+          data: {
+            ...entity,
+            projectId,
+          },
+        });
+
+        await transaction.endUsersOnBusinesses.create({
+          data: {
+            endUserId: endUser.id,
+            businessId: workflowRuntimeData.businessId,
+            position: entityType,
+          },
+        });
 
-      await transaction.endUsersOnBusinesses.create({
-        data: {
-          endUserId: endUser.id,
-          businessId: workflowRuntimeData.businessId,
-          position: entityType,
-        },
-      });
+        this.logger.debug(
+          `Successfully created entity ${endUser.id} for workflow ${workflowId}`,
+        );
 
-      return {
-        entityId: endUser.id,
-      };
+        return {
+          entityId: endUser.id,
+        };
+      } catch (error) {
+        this.logger.error(
+          `Failed to create entity for workflow ${workflowId}: ${error.message}`,
+        );
+        throw error;
+      }
     });
   }

56-70: Add existence check and logging.

Consider:

  1. Validating that the entity exists before deletion.
  2. Adding logging for debugging purposes.
  3. Adding error handling for database operations.

Apply this diff to improve the implementation:

   async deleteEntity(entityId: string) {
+    this.logger.debug(`Deleting entity ${entityId}`);
+
     return await this.prismaService.$transaction(async transaction => {
+      const endUser = await transaction.endUser.findUnique({
+        where: { id: entityId },
+      });
+
+      if (!endUser) {
+        throw new NotFoundException(`Entity ${entityId} not found`);
+      }
+
+      try {
         await transaction.endUsersOnBusinesses.deleteMany({
           where: {
             endUserId: entityId,
           },
         });
 
         await transaction.endUser.delete({
           where: {
             id: entityId,
           },
         });
+
+        this.logger.debug(`Successfully deleted entity ${entityId}`);
+      } catch (error) {
+        this.logger.error(
+          `Failed to delete entity ${entityId}: ${error.message}`,
+        );
+        throw error;
+      }
     });
   }
packages/ui/src/common/hooks/useHttp/utils/request.ts (1)

9-36: Enhance error handling and type safety.

The function could benefit from improved error handling and type safety.

Apply this diff to enhance the implementation:

 export const request = async (
-  request: TReuqestParams,
+  request: TRequestParams,
   metadata: AnyObject = {},
-  data?: any,
+  data?: unknown,
   params?: AnyObject,
 ) => {
   const { url: _url, headers = {}, method, timeout = 5000 } = request;

   const formattedUrl = formatString(_url, { ...metadata, ...params });
   const formattedHeaders = formatHeaders(headers, metadata);

   try {
     const response = await axios({
       url: formattedUrl,
       method,
       headers: formattedHeaders,
       data,
       timeout,
     });

     return response.data;
   } catch (error) {
-    console.error('Failed to perform request.', error);
+    if (axios.isAxiosError(error)) {
+      console.error('Failed to perform request:', {
+        status: error.response?.status,
+        data: error.response?.data,
+        message: error.message,
+      });
+    } else {
+      console.error('Failed to perform request:', error);
+    }

     throw error;
   }
 };
packages/ui/src/common/hooks/useHttp/useHttp.ts (1)

32-36: Improve error handling.

The error handling could be more specific and informative.

-        return params.resultPath ? get(response, params.resultPath) : response;
+        const result = params.resultPath ? get(response, params.resultPath) : response;
+        if (result === undefined && params.resultPath) {
+          console.warn(`Result path "${params.resultPath}" not found in response`);
+        }
+        return result;
       } catch (error) {
-        console.error(error);
+        const message = error instanceof Error ? error.message : 'Unknown error occurred';
+        console.error('HTTP request failed:', message);
         setResponseError(error as Error);
+        throw error;
       }
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/hooks/useEntityFieldGroupList/useEntityFieldGroupList.ts (1)

32-56: Enhance error handling and type safety.

The error handling could be more specific and the type safety could be improved.

 const removeItem = useCallback(
   async (id: string) => {
     if (!Array.isArray(value)) {
+      console.warn('Value is not an array:', value);
       return;
     }

     const entity = value.find(entity => entity.__id === id);
+    
+    if (!entity) {
+      console.warn(`Entity with id ${id} not found`);
+      return;
+    }

     if (entity?.id) {
       try {
         await deleteEntity({}, { params: { entityId: entity.id } });
       } catch (error) {
+        const message = error instanceof Error ? error.message : 'Unknown error occurred';
         toast.error('Failed to delete entity.');
-
-        console.error(error);
+        console.error('Failed to delete entity:', message);

         return;
       }
     }

     const newValue = value.filter(entity => entity.__id !== id);
     onChange(newValue);
   },
   [value, deleteEntity, onChange],
 );
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/EntityFieldGroup.tsx (1)

101-101: Consider moving Toaster to a higher level.

Having a Toaster component within a form field could lead to multiple toaster instances if the field is rendered multiple times.

Consider moving the Toaster component to a higher level in the component tree, such as the form root or application root.

packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/EntityFields.tsx (1)

125-130: Improve error handling.

The error handling could be more informative and consistent.

Consider these improvements:

-            toast.error('Failed to create entity.');
-            console.error(error);
+            const errorMessage = error instanceof Error ? error.message : 'Failed to create entity';
+            toast.error(errorMessage);
+            console.error('Entity creation failed:', error);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f62566b and cedaeb7.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (14)
  • packages/ui/src/common/hooks/useHttp/types.ts (1 hunks)
  • packages/ui/src/common/hooks/useHttp/useHttp.ts (1 hunks)
  • packages/ui/src/common/hooks/useHttp/utils/request.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/EntityFieldGroup.stories.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/EntityFieldGroup.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/EntityFields.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/utils/transform.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/hooks/useEntityFieldGroupList/useEntityFieldGroupList.ts (1 hunks)
  • services/workflows-service/prisma/data-migrations (1 hunks)
  • services/workflows-service/src/collection-flow/collection-flow-entity.service.ts (1 hunks)
  • services/workflows-service/src/collection-flow/collection-flow.module.ts (4 hunks)
  • services/workflows-service/src/collection-flow/controllers/collection-flow.entity.controller.ts (1 hunks)
  • services/workflows-service/src/collection-flow/dto/create-entity-input.dto.ts (1 hunks)
  • services/workflows-service/src/end-user/dtos/end-user-create.ts (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • services/workflows-service/prisma/data-migrations
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ui/src/common/hooks/useHttp/types.ts
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: test_windows
  • GitHub Check: test_linux
  • GitHub Check: Analyze (javascript)
  • GitHub Check: build (windows-latest)
  • GitHub Check: build (ubuntu-latest)
  • GitHub Check: lint
  • GitHub Check: format
🔇 Additional comments (3)
services/workflows-service/src/collection-flow/collection-flow.module.ts (1)

43-44: LGTM! Well-structured module integration.

The new entity-related components (CollectionFlowEntityService and CollectionFlowEntityController) are properly integrated into the module following NestJS best practices.

Also applies to: 66-66, 96-96

packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/EntityFieldGroup.tsx (1)

79-80: Fix inconsistent key usage.

The component uses entity.id as key but entity.__id for other operations. This could lead to rendering issues.

-            key={entity.id}
+            key={entity.__id!}
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/EntityFields.tsx (1)

136-136: Review useCallback dependencies.

The createEntityOnLockTask callback is missing toast in its dependency array.

Comment on lines +94 to +115
export const EntityFieldGroup = () => {
const [context, setContext] = useState<AnyObject>(initialContext);

return (
<div className="flex h-screen w-full flex-row flex-nowrap gap-4">
<div className="w-1/2">
<DynamicFormV2
elements={defaultSchema}
values={context}
onSubmit={() => {
console.log('onSubmit');
}}
onChange={setContext}
// onEvent={console.log}
/>
</div>
<div className="w-1/2">
<JSONEditorComponent value={context} readOnly />
</div>
</div>
);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Extract common layout and behavior into a reusable component.

The EntityFieldGroup, UbosFieldGroup, and DirectorsFieldGroup components share identical layout and behavior. Extract these into a reusable component to improve maintainability.

Create a new component:

interface FormGroupProps {
  schema: Array<IFormElement<any, any>>;
  initialContext: AnyObject;
  metadata?: AnyObject;
}

const FormGroup: React.FC<FormGroupProps> = ({ schema, initialContext, metadata }) => {
  const [context, setContext] = useState<AnyObject>(initialContext);

  return (
    <div className="flex h-screen w-full flex-row flex-nowrap gap-4">
      <div className="w-1/2">
        <DynamicFormV2
          elements={schema}
          values={context}
          onSubmit={() => {}}
          onChange={setContext}
          metadata={metadata}
          validationParams={{
            validateOnChange: true,
            validateOnBlur: true,
            abortEarly: false,
            abortAfterFirstError: true,
            validationDelay: 300,
          }}
        />
      </div>
      <div className="w-1/2">
        <JSONEditorComponent value={context} readOnly />
      </div>
    </div>
  );
};

Then simplify the existing components:

export const EntityFieldGroup = () => (
  <FormGroup schema={defaultSchema} initialContext={initialContext} />
);

export const UbosFieldGroup = () => (
  <FormGroup
    schema={ubosSchema}
    initialContext={initialUbosContext}
    metadata={metadata}
  />
);

export const DirectorsFieldGroup = () => (
  <FormGroup schema={directorsSchema} initialContext={initialContext} />
);

Also applies to: 427-456, 544-565

Comment on lines 422 to 425
const metadata = {
apiUrl: 'http://localhost:3000/api/v1/',
token: 'TOKEN',
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove hardcoded API configuration.

Hardcoding API URLs and tokens in source code is a security risk. Consider:

  1. Moving configuration to environment variables
  2. Using a configuration service
  3. Implementing proper token management

Apply this diff:

-const metadata = {
-  apiUrl: 'http://localhost:3000/api/v1/',
-  token: 'TOKEN',
-};
+const metadata = {
+  apiUrl: process.env.REACT_APP_API_URL,
+  token: process.env.REACT_APP_API_TOKEN,
+};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const metadata = {
apiUrl: 'http://localhost:3000/api/v1/',
token: 'TOKEN',
};
const metadata = {
apiUrl: process.env.REACT_APP_API_URL,
token: process.env.REACT_APP_API_TOKEN,
};

Comment on lines +441 to +447
validationParams={{
validateOnChange: true,
validateOnBlur: true,
abortEarly: false,
abortAfterFirstError: true,
validationDelay: 300,
}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Apply consistent validation configuration across all form components.

The UbosFieldGroup has detailed validation configuration that should be applied to all form components for consistent behavior.

Apply the same validation configuration to EntityFieldGroup and DirectorsFieldGroup:

         <DynamicFormV2
           elements={defaultSchema}
           values={context}
           onSubmit={() => {}}
           onChange={setContext}
+          validationParams={{
+            validateOnChange: true,
+            validateOnBlur: true,
+            abortEarly: false,
+            abortAfterFirstError: true,
+            validationDelay: 300,
+          }}
         />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
validationParams={{
validateOnChange: true,
validateOnBlur: true,
abortEarly: false,
abortAfterFirstError: true,
validationDelay: 300,
}}
<DynamicFormV2
elements={defaultSchema}
values={context}
onSubmit={() => {}}
onChange={setContext}
validationParams={{
validateOnChange: true,
validateOnBlur: true,
abortEarly: false,
abortAfterFirstError: true,
validationDelay: 300,
}}
/>

Comment on lines +14 to +24
@Post()
async createEntity(@TokenScope() tokenScope: ITokenScope, @Body() body: CreateEntityInputDto) {
const { entityType, entity } = body;

return this.collectionFlowEntityService.createEntity(
tokenScope.workflowRuntimeDataId,
entityType,
entity,
tokenScope.projectId,
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for invalid token scope.

The method should validate that workflowRuntimeDataId and projectId exist in the token scope before using them.

Apply this diff to add error handling:

   @Post()
   async createEntity(@TokenScope() tokenScope: ITokenScope, @Body() body: CreateEntityInputDto) {
     const { entityType, entity } = body;
+    if (!tokenScope.workflowRuntimeDataId || !tokenScope.projectId) {
+      throw new BadRequestException('Invalid token scope: missing required fields');
+    }
 
     return this.collectionFlowEntityService.createEntity(
       tokenScope.workflowRuntimeDataId,
       entityType,
       entity,
       tokenScope.projectId,
     );
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Post()
async createEntity(@TokenScope() tokenScope: ITokenScope, @Body() body: CreateEntityInputDto) {
const { entityType, entity } = body;
return this.collectionFlowEntityService.createEntity(
tokenScope.workflowRuntimeDataId,
entityType,
entity,
tokenScope.projectId,
);
}
@Post()
async createEntity(@TokenScope() tokenScope: ITokenScope, @Body() body: CreateEntityInputDto) {
const { entityType, entity } = body;
if (!tokenScope.workflowRuntimeDataId || !tokenScope.projectId) {
throw new BadRequestException('Invalid token scope: missing required fields');
}
return this.collectionFlowEntityService.createEntity(
tokenScope.workflowRuntimeDataId,
entityType,
entity,
tokenScope.projectId,
);
}

Comment on lines +26 to +29
@Delete(':entityId')
async deleteEntity(@Param('entityId') entityId: string) {
return this.collectionFlowEntityService.deleteEntity(entityId);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add authorization check and input validation.

The delete operation should:

  1. Verify that the user has permission to delete the entity.
  2. Validate the entityId format.

Apply this diff to add authorization and validation:

   @Delete(':entityId')
-  async deleteEntity(@Param('entityId') entityId: string) {
+  async deleteEntity(
+    @TokenScope() tokenScope: ITokenScope,
+    @Param('entityId', new ParseUUIDPipe()) entityId: string
+  ) {
+    if (!tokenScope.workflowRuntimeDataId || !tokenScope.projectId) {
+      throw new BadRequestException('Invalid token scope: missing required fields');
+    }
+
     return this.collectionFlowEntityService.deleteEntity(entityId);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Delete(':entityId')
async deleteEntity(@Param('entityId') entityId: string) {
return this.collectionFlowEntityService.deleteEntity(entityId);
}
@Delete(':entityId')
async deleteEntity(
@TokenScope() tokenScope: ITokenScope,
@Param('entityId', new ParseUUIDPipe()) entityId: string
) {
if (!tokenScope.workflowRuntimeDataId || !tokenScope.projectId) {
throw new BadRequestException('Invalid token scope: missing required fields');
}
return this.collectionFlowEntityService.deleteEntity(entityId);
}

Comment on lines +5 to +16
export const transform = async (context: AnyObject, entity: IEntity, expression: string) => {
const transfomer = jsonata(expression);

const transformerPayload = {
context,
entity,
};

const transformResult = await transfomer.evaluate(transformerPayload);

return transformResult;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fix typo and add error handling.

The variable name transfomer has a typo, and the function should handle potential errors during expression evaluation.

Apply this diff to fix the issues:

 export const transform = async (context: AnyObject, entity: IEntity, expression: string) => {
-  const transfomer = jsonata(expression);
+  const transformer = jsonata(expression);

   const transformerPayload = {
     context,
     entity,
   };

-  const transformResult = await transfomer.evaluate(transformerPayload);
-
-  return transformResult;
+  try {
+    return await transformer.evaluate(transformerPayload);
+  } catch (error) {
+    console.error('Failed to evaluate expression:', error);
+    throw error;
+  }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const transform = async (context: AnyObject, entity: IEntity, expression: string) => {
const transfomer = jsonata(expression);
const transformerPayload = {
context,
entity,
};
const transformResult = await transfomer.evaluate(transformerPayload);
return transformResult;
};
export const transform = async (context: AnyObject, entity: IEntity, expression: string) => {
const transformer = jsonata(expression);
const transformerPayload = {
context,
entity,
};
try {
return await transformer.evaluate(transformerPayload);
} catch (error) {
console.error('Failed to evaluate expression:', error);
throw error;
}
};

import { IHttpParams } from '../types';
import { formatHeaders } from './format-headers';

export type TReuqestParams = Omit<IHttpParams, 'resultPath'>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fix typo in type name.

The type name TReuqestParams has a typo.

-export type TReuqestParams = Omit<IHttpParams, 'resultPath'>;
+export type TRequestParams = Omit<IHttpParams, 'resultPath'>;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export type TReuqestParams = Omit<IHttpParams, 'resultPath'>;
export type TRequestParams = Omit<IHttpParams, 'resultPath'>;

Comment on lines +23 to +30
{
...params,
url: params.url,
},
metadata,
requestPayload,
other?.params,
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove redundant spread and improve type safety.

The spread of params is redundant since url is explicitly set, and the function could benefit from improved type safety.

 const response = await request(
-  {
-    ...params,
-    url: params.url,
-  },
+  params,
   metadata,
   requestPayload,
   other?.params,
 );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{
...params,
url: params.url,
},
metadata,
requestPayload,
other?.params,
);
params,
metadata,
requestPayload,
other?.params,
);

Comment on lines +25 to +30
const addItem = useCallback(async () => {
const initialEntity = {
__id: crypto.randomUUID(),
};
onChange([...(value || []), initialEntity]);
}, [value, onChange]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve type safety for initial entity.

The initial entity should conform to the IEntity interface.

 const addItem = useCallback(async () => {
-  const initialEntity = {
+  const initialEntity: IEntity = {
     __id: crypto.randomUUID(),
+    __isLocked: false,
+    __isCreated: true,
   };
   onChange([...(value || []), initialEntity]);
 }, [value, onChange]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const addItem = useCallback(async () => {
const initialEntity = {
__id: crypto.randomUUID(),
};
onChange([...(value || []), initialEntity]);
}, [value, onChange]);
const addItem = useCallback(async () => {
- const initialEntity = {
- __id: crypto.randomUUID(),
- };
- onChange([...(value || []), initialEntity]);
+ const initialEntity: IEntity = {
+ __id: crypto.randomUUID(),
+ __isLocked: false,
+ __isCreated: true,
+ };
+ onChange([...(value || []), initialEntity]);
}, [value, onChange]);

Comment on lines 73 to 89
// // Entities with documents
// const entitiesWithDocuments = entities.map((entity: IEntity, index: number) => {
// const entityWithDocument = { ...entity } as Record<string, any>;

// documentFieldDefinitons.forEach(documentDefinition => {
// const documentDestination = formatValueDestination(
// documentDefinition.valueDestination,
// [...(stack || []), index],
// );

// const documentFile = get(context, documentDestination);

// entityWithDocument[(documentDefinition.params as any).template.id] = documentFile;
// });

// return entityWithDocument;
// });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove commented out code.

The commented out code block appears to be a placeholder and is no longer needed based on the past review comments.

Remove this commented out code block to improve code readability and maintainability.

* feat(workflows-service): implemented document controller, service, repository, and dto

* fix(workflows-service): added cascase on delete

* refactor(workflows-service): removed uploadFile method from collection flow service

* feat(workflows-service): added the ability to reupload document (#3019)

* feat: implemented documents upload

* feat: reworked creation ui

* feat: implemented document creation & deletion

* feat: finalized entity group field

* fix: fixed tests

* fix: cleanup

* fix: format fix

* fix: fixed build

---------

Co-authored-by: Omri Levy <omri.levy0197@gmail.com>
Co-authored-by: Omri Levy <61207713+Omri-Levy@users.noreply.github.com>
Co-authored-by: Shane <66246046+shanegrouber@users.noreply.github.com>
url: '{apiUrl}collection-flow/entity/{entityId}',
method: 'DELETE',
headers: {
Authorization: 'Bearer {token}',

Check failure

Code scanning / CodeQL

Hard-coded credentials Critical

The hard-coded value "Bearer {token}" is used as
authorization header
.
url: '{apiUrl}collection-flow/entity/{entityId}',
method: 'PUT',
headers: {
Authorization: 'Bearer {token}',

Check failure

Code scanning / CodeQL

Hard-coded credentials Critical

The hard-coded value "Bearer {token}" is used as
authorization header
.
url: '{apiUrl}collection-flow/files',
method: 'POST',
headers: {
Authorization: 'Bearer {token}',

Check failure

Code scanning / CodeQL

Hard-coded credentials Critical

The hard-coded value "Bearer {token}" is used as
authorization header
.
url: '{apiUrl}collection-flow/files',
method: 'DELETE',
headers: {
Authorization: 'Bearer {token}',

Check failure

Code scanning / CodeQL

Hard-coded credentials Critical

The hard-coded value "Bearer {token}" is used as
authorization header
.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

🔭 Outside diff range comments (6)
packages/ui/src/components/organisms/Form/DynamicForm/fields/DocumentField/hooks/useDocumentUpload/helpers/create-or-update-fileid-or-file-in-documents/create-or-update-fileid-or-file-in-documents.unit.test.ts (1)

30-31: Remove @ts-ignore comments by properly typing the assertions.

Multiple @ts-ignore comments suggest underlying type issues that should be addressed rather than suppressed. Consider adding proper type definitions or type guards for the document pages structure.

Example approach:

interface DocumentPage {
  ballerineFileId: string | File | null;
}

interface Document {
  id: string;
  type: string;
  pages: DocumentPage[];
}

// Then use these types in your assertions
expect((result[0] as Document).pages[0].ballerineFileId).toBe('test-file-id');

Also applies to: 47-48, 64-65, 74-75, 116-117

services/workflows-service/src/storage/storage.service.ts (3)

122-130: Fix method name typo and improve path handling.

The method name has a typo (FilePAth) and the path handling logic seems incorrect:

  1. The method returns the original path if it's NOT absolute (which seems counterintuitive)
  2. Using os.homedir() for root directory could lead to security issues
-  private __getAbsoluteFilePAth(filePath: string) {
-    if (!path.isAbsolute(filePath)) {
-      return filePath;
-    }
-
-    const rootDir = path.parse(os.homedir()).root;
-
-    return path.join(rootDir, filePath);
+  private __getAbsoluteFilePath(filePath: string) {
+    if (path.isAbsolute(filePath)) {
+      return filePath;
+    }
+
+    // Use a dedicated application directory instead of system root
+    const appStorageDir = process.env.APP_STORAGE_DIR || '/app/storage';
+    return path.join(appStorageDir, filePath);

146-152: Handle large files in base64 conversion.

The current implementation reads the entire file into memory which could cause issues with large files.

Consider using streams for large file handling:

-  fileToBase64(filepath: string): string {
-    const fileBuffer = readFileSync(filepath);
-
-    const base64String = fileBuffer.toString('base64');
-
-    return base64String;
+  async fileToBase64(filepath: string): Promise<string> {
+    const { createReadStream } = await import('fs');
+    const { Transform } = await import('stream');
+    const { promisify } = await import('util');
+    const pipeline = promisify(require('stream').pipeline);
+
+    const base64Encoder = new Transform({
+      transform(chunk, encoding, callback) {
+        callback(null, chunk.toString('base64'));
+      }
+    });
+
+    let base64String = '';
+    await pipeline(
+      createReadStream(filepath),
+      base64Encoder,
+      new Transform({
+        transform(chunk, encoding, callback) {
+          base64String += chunk;
+          callback(null);
+        }
+      })
+    );
+
+    return base64String;
   }

132-140: Improve temporary file handling.

The current implementation of downloading files to temporary directory has potential issues:

  1. No cleanup of temporary files
  2. Potential file name collisions
  3. No file size limits

Consider implementing proper temporary file management:

   private async __downloadFileFromRemote(persistedFile: File) {
-    const localeFilePath = `${os.tmpdir()}/${persistedFile.id}`;
+    const { tmpdir } = os;
+    const { mkdtemp, rm } = await import('fs/promises');
+    
+    // Create a unique temporary directory
+    const tempDir = await mkdtemp(`${tmpdir()}/download-`);
+    const localFilePath = path.join(tempDir, persistedFile.id);
+    
+    try {
     const downloadedFilePath = await new HttpFileService({
       client: this.httpService,
       logger: this.logger,
-    }).download(persistedFile.uri, localeFilePath);
+    }).download(persistedFile.uri, localFilePath, {
+      maxSize: 100 * 1024 * 1024, // 100MB limit
+    });
+    
+    return downloadedFilePath;
+    } finally {
+      // Clean up temporary directory
+      await rm(tempDir, { recursive: true, force: true });
+    }
-    return downloadedFilePath;
   }
packages/ui/src/components/organisms/Form/DynamicForm/fields/DocumentField/hooks/useDocumentUpload/useDocumentUpload.ts (1)

71-73: Improve error handling.

Replace console.error with proper error handling:

  1. Consider using a centralized error handling service
  2. Propagate errors to the UI for better user feedback
  3. Remove duplicate error handling code
-console.error('Failed to upload file.', error);
+const errorMessage = error instanceof Error ? error.message : 'Failed to upload file';
+handleError({
+  error,
+  message: errorMessage,
+  context: 'Document upload',
+  showToast: true,
+});
+throw new DocumentUploadError(errorMessage);

Also applies to: 108-110

services/workflows-service/prisma/migrations/20250129160127_documents_issuing_country/migration.sql (1)

1-9: Provide a default value or migration strategy for existing data.

Adding a required column without a default value will fail if the table contains data. Consider:

  1. Adding a default value for existing records
  2. Making the column nullable initially
  3. Implementing a data migration strategy

Apply this diff:

-- AlterTable
-ALTER TABLE "Document" ADD COLUMN     "issuingCountry" TEXT NOT NULL;
+-- First add the column as nullable
+ALTER TABLE "Document" ADD COLUMN "issuingCountry" TEXT;
+
+-- Update existing records with a default value
+UPDATE "Document" SET "issuingCountry" = 'UNKNOWN' WHERE "issuingCountry" IS NULL;
+
+-- Then make it non-nullable
+ALTER TABLE "Document" ALTER COLUMN "issuingCountry" SET NOT NULL;
♻️ Duplicate comments (1)
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/EntityFieldGroup.tsx (1)

85-86: ⚠️ Potential issue

Fix inconsistent key usage.

The component uses entity.id as key but entity.__id for other operations. This could lead to rendering issues.

-            key={entity.id}
+            key={entity.__id!}
🧹 Nitpick comments (33)
packages/ui/src/components/organisms/Form/DynamicForm/fields/DocumentField/hooks/useDocumentUpload/helpers/create-or-update-fileid-or-file-in-documents/create-or-update-fileid-or-file-in-documents.unit.test.ts (1)

21-21: Consider improving type safety by avoiding unknown assertions.

The type assertions using as unknown as suggest potential type safety issues. Consider creating proper type definitions or using type guards instead of force-casting types.

-    } as unknown as IDocumentFieldParams,
+    } satisfies Partial<IDocumentFieldParams>,

-    } as unknown as IFormElement<'documentfield', IDocumentFieldParams>;
+    } satisfies Partial<IFormElement<'documentfield', IDocumentFieldParams>>;

Also applies to: 107-107

services/workflows-service/src/storage/storage.service.ts (2)

1-1: Remove unused imports.

The following imports appear to be unused in the code:

  • NotFoundException from @nestjs/common (line 1) - the code uses errors.NotFoundException instead
  • randomUUID from crypto (line 21)
-import { Injectable, NotFoundException } from '@nestjs/common';
+import { Injectable } from '@nestjs/common';
-import { randomUUID } from 'crypto';

Also applies to: 21-21


68-120: Improve error handling in file content fetching.

The fetchFileContent method could benefit from more specific error handling:

  1. No validation of the format parameter
  2. No handling of S3 download failures
  3. No handling of HTTP download failures

Consider adding more specific error handling:

   async fetchFileContent({
     id,
     projectIds,
     format,
   }: {
     id: string;
     projectIds: TProjectIds;
-    format?: string;
+    format?: 'signed-url' | 'raw';
   }) {
     const persistedFile = await this.getFileById({ id }, projectIds, {});

     if (!persistedFile) {
       throw new errors.NotFoundException('file not found');
     }

+    try {
       let mimeType =
         persistedFile.mimeType ||
         mime.getType(persistedFile.fileName || persistedFile.uri || '') ||
         'image/jpeg';

       if (persistedFile.fileNameInBucket && format === 'signed-url') {
         const signedUrl = await createPresignedUrlWithClient({
           bucketName: AwsS3FileConfig.getBucketName(process.env) as string,
           fileNameInBucket: persistedFile.fileNameInBucket,
           mimeType,
         });

         return { signedUrl, mimeType };
       }

       mimeType ||= 'application/octet-stream';

       if (persistedFile.fileNameInBucket) {
         const localFilePath = await downloadFileFromS3(
           AwsS3FileConfig.getBucketName(process.env) as string,
           persistedFile.fileNameInBucket,
         );

         return { filePath: path.resolve('/', localFilePath), mimeType };
       }

       if (!isBase64(persistedFile.uri) && this._isUri(persistedFile)) {
         const downloadFilePath = await this.__downloadFileFromRemote(persistedFile);

         const filePath = this.__getAbsoluteFilePAth(downloadFilePath);

         return { filePath: filePath, mimeType };
       }

       const filePath = this.__getAbsoluteFilePAth(persistedFile.fileNameOnDisk);

       return { filePath: filePath, mimeType };
+    } catch (error) {
+      this.logger.error('Failed to fetch file content', {
+        error,
+        fileId: id,
+        format,
+      });
+      if (error instanceof errors.NotFoundException) {
+        throw error;
+      }
+      throw new errors.InternalServerError('Failed to fetch file content');
+    }
   }
services/workflows-service/src/document-file/dtos/document-file.dto.ts (2)

5-13: Consider adding more specific validation rules.

While the basic types are correct, consider enhancing data integrity with additional validation rules:

  • Add UUID format validation for id, documentId, fileId, and projectId
  • Add minimum value validation for page (e.g., >= 1)
 export const DocumentFileSchema = Type.Object({
-  id: Type.String(),
+  id: Type.String({ format: 'uuid' }),
   type: Type.Enum(DocumentFileType),
   variant: Type.Enum(DocumentFileVariant),
-  page: Type.Integer(),
+  page: Type.Integer({ minimum: 1 }),
-  documentId: Type.String(),
-  fileId: Type.String(),
-  projectId: Type.String(),
+  documentId: Type.String({ format: 'uuid' }),
+  fileId: Type.String({ format: 'uuid' }),
+  projectId: Type.String({ format: 'uuid' }),
 });

32-34: Add UUID format validation for the id parameter.

Consider adding format validation to ensure the id parameter is a valid UUID:

 export const DocumentFileParamsSchema = Type.Object({
-  id: Type.String(),
+  id: Type.String({ format: 'uuid' }),
 });
services/workflows-service/src/providers/file/file.service.ts (2)

373-374: Fix grammatical error in exception message.

The error message should be "Workflow doesn't exist" (without 's').

-      throw new NotFoundException("Workflow doesn't exists");
+      throw new NotFoundException("Workflow doesn't exist");

368-369: Add error handling for customer retrieval.

The customer retrieval could fail if the project doesn't exist.

-    const customer = await this.customerService.getByProjectId(projectId);
+    const customer = await this.customerService.getByProjectId(projectId).catch(error => {
+      throw new NotFoundException(`Customer not found for project ${projectId}`);
+    });
services/workflows-service/src/collection-flow/controllers/collection-flow.files.controller.ts (1)

55-108: Consider more robust handling of JSON parse errors.
While zod will throw an error if the JSON for “metadata” or “properties” is invalid, consider providing a clearer error message or explicitly handling parse exceptions to improve user feedback. Additionally, returning only the first element from an array named “documentsCreationResults” could be confusing—ensure that the controller’s return value matches the intended usage and naming.

services/workflows-service/src/document/document.controller.external.ts (1)

37-107: Watch out for invalid JSON in “metadata” and “properties.”
Like in the other controller, zod throws an error on bad JSON. This is typically acceptable, but consider layering additional context or custom error messages if parse failures are common.

services/workflows-service/src/document/document.service.ts (4)

24-108: Add transaction or cleanup approach for partial failures.
Should the file upload succeed but the document creation fail afterward, consider using a transaction or manual cleanup to avoid orphaned file references. This ensures data integrity across system boundaries.


110-136: Potential performance considerations on large result sets.
Fetching all documents (plus signed URLs) can be expensive if the dataset scales. Consider caching or pagination for better performance.


189-220: Consider the cost of parallel file content retrieval.
For many documents and files, you may observe performance constraints due to multiple simultaneous signed-URL fetches.


222-266: Reupload partial failure scenario.
If file re-uploading succeeds but subsequent steps fail, partial data inconsistencies could remain. Incorporate transaction usage or manual rollback logic if needed.

packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldProvider/hooks/external/useEntityField/useEntityField.ts (1)

7-9: Update error message to match context name.

The error message references "EntityFieldGroupTypeProvider" but the context is named "EntityFieldContext". This inconsistency could be confusing.

-    throw new Error('useEntityField must be used within a EntityFieldGroupTypeProvider');
+    throw new Error('useEntityField must be used within an EntityFieldContext.Provider');
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldProvider/hooks/external/useEntityFieldGroupType/useEntityFieldGroupType.ts (2)

4-12: Consider consolidating duplicate hooks.

This hook is identical to useEntityField and returns the same context. Consider either:

  1. Consolidating these hooks into a single hook if they serve the same purpose, or
  2. Making them more specific by returning only the relevant parts of the context

Example of option 2:

export const useEntityFieldGroupType = () => {
  const context = useContext(EntityFieldContext);

  if (!context) {
    throw new Error('useEntityFieldGroupType must be used within an EntityFieldContext.Provider');
  }

  return context.entityFieldGroupType;
};

7-9: Update error message to match context name.

The error message references "EntityFieldGroupTypeProvider" but the context is named "EntityFieldContext". This inconsistency could be confusing.

-    throw new Error('useEntityFieldGroupType must be used within a EntityFieldGroupTypeProvider');
+    throw new Error('useEntityFieldGroupType must be used within an EntityFieldContext.Provider');
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/helpers/update-entities.ts (1)

3-16: Consider updating all properties from updatedEntity.

The current implementation only updates the id property while preserving other properties. This might be insufficient if other properties in the updatedEntity need to be reflected in the updated entity.

Apply this diff to update all properties from updatedEntity:

 export const updateEntities = (entitiesList: IEntity[], updatedEntity: IEntity) => {
   return entitiesList.map(entity => {
     if (entity.__id === updatedEntity.__id) {
-      const newEntity = {
-        ...entity,
-        id: updatedEntity.id,
-      };
-
-      return newEntity;
+      return {
+        ...entity,
+        ...updatedEntity,
+      };
     }

     return entity;
   });
 };
services/workflows-service/src/document/dtos/document.dto.ts (1)

4-18: LGTM! Consider adding validation constraints.

The schema is well-structured. Consider adding validation constraints for fields like version (minimum value), issuingCountry (format), etc.

Example constraints:

   version: Type.Integer(),
+  version: Type.Integer({ minimum: 1 }),
   issuingCountry: Type.String(),
+  issuingCountry: Type.String({ pattern: '^[A-Z]{2}$' }),
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/hooks/useIsEntityFieldsValid/useIsEntityFieldsValid.ts (1)

30-30: Consider returning validation errors for debugging.

Instead of just returning a boolean, consider returning both the validation status and any errors for debugging purposes.

-    return validationErrors?.length === 0;
+    return {
+      isValid: validationErrors?.length === 0,
+      errors: validationErrors,
+    };
services/workflows-service/src/document-file/document-file.service.ts (1)

44-51: Add soft delete consideration.

Consider implementing soft delete to maintain data history and prevent accidental permanent deletions.

Consider adding a deletedAt timestamp instead of performing hard deletes. This allows for data recovery and maintains audit trails.

packages/ui/src/components/organisms/Form/DynamicForm/hooks/internal/useClear/value-cleaners/documentfield-value-cleaner.unit.test.ts (1)

13-19: Improve mock data structure.

Using type assertions (as) in mock data can hide potential type issues. Consider providing complete mock data that matches the interface.

Replace type assertions with complete mock data:

  params: {
    template: {
      id: 'template-1',
+     category: 'test',
+     type: 'document',
+     issuer: { country: 'US' },
+     version: '1.0',
+     issuingVersion: '1.0',
+     properties: {}
-   } as IDocumentTemplate,
+   },
    documentType: 'document',
    documentVariant: 'variant',
    httpParams: {
+     createDocument: { url: '/api/documents', method: 'POST' },
+     deleteDocument: { url: '/api/documents', method: 'DELETE' }
-   } as IDocumentFieldParams['httpParams'],
+   },
-   } as IDocumentFieldParams,
+   },
services/workflows-service/src/document/document.repository.ts (1)

53-69: Consider using update instead of updateMany for single document updates.

The updateById method uses updateMany but the method name and ID parameter suggest it's meant for single document updates. This could be misleading.

Consider this alternative implementation:

-  async updateById(
+  async updateById(
     id: string,
     projectIds: TProjectId[],
     data: Prisma.DocumentUpdateInput,
-    args?: Prisma.DocumentUpdateManyArgs,
+    args?: Prisma.DocumentUpdateArgs,
     transaction: PrismaTransactionClient = this.prismaService,
   ) {
-    return await transaction.document.updateMany({
+    return await transaction.document.update({
       ...args,
       where: {
         ...args?.where,
         id,
-        projectId: { in: projectIds },
       },
       data,
     });
   }
services/workflows-service/src/document-file/document-file.repository.ts (1)

48-62: Consider consistent implementation with DocumentRepository.

The updateById implementation differs from DocumentRepository. While this implementation is correct for single document updates, we should maintain consistency across repositories.

Either:

  1. Update DocumentRepository.updateById to match this implementation (preferred), or
  2. Update this implementation to match DocumentRepository.updateById
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/helpers/build-documents-creation-payload.ts (1)

38-68: Consider performance optimization for nested loops.

The nested loops could impact performance with large datasets. Consider using array methods for better readability and potentially better performance.

Here's an alternative implementation:

-  // Outer loop for correct index calculation
-  for (let entityIndex = 0; entityIndex < entities.length; entityIndex++) {
-    // Inner loop for document elements, each entity can have multiple document fields
-    for (const documentElement of documentElements) {
+  return entities.flatMap((_, entityIndex) =>
+    documentElements
+      .filter(documentElement => documentElement?.params?.template)
+      .map(documentElement => {
+        if (!documentElement?.params?.template) {
+          console.warn('No template found for document field', documentElement);
+          return null;
+        }

         const documentDestination = formatValueDestination(documentElement.valueDestination, [
           ...(stack || []),
           entityIndex,
         ]);

         const documentFile = getFileOrFileIdFromDocumentsList(
           get(context, documentDestination),
           documentElement,
         );

         if (!documentFile || !(documentFile instanceof File)) {
-          continue;
+          return null;
         }

         const payload = buildDocumentFormData(documentElement, { entityId }, documentFile);

-        documentPayload.push({
+        return {
           payload,
           documentDefinition: documentElement,
           valueDestination: documentDestination,
-        });
+        };
-      }
-    }
-  }
-
-  return documentPayload;
+      })
+      .filter(Boolean)
+  );
packages/ui/src/components/organisms/Form/DynamicForm/fields/DocumentField/hooks/useDocumentUpload/helpers/remove-document-from-list-by-template-id/remove-document-from-list-by-template-id.unit.test.ts (1)

7-30: Consider using test data factories.

Repeated test data could be extracted into a factory function to improve maintainability and reduce duplication.

+const createTestDocument = (id: string): IDocumentTemplate => ({
+  id,
+  category: 'test',
+  type: 'test',
+  issuer: {
+    country: 'test',
+  },
+  version: 1,
+  issuingVersion: 1,
+  properties: {},
+});

 it('should remove document with matching template id from list', () => {
-  const documents = [
-    {
-      id: 'doc1',
-      category: 'test',
-      // ... rest of the properties
-    },
-    {
-      id: 'doc2',
-      category: 'test',
-      // ... rest of the properties
-    },
-  ] as IDocumentTemplate[];
+  const documents = [
+    createTestDocument('doc1'),
+    createTestDocument('doc2'),
+  ];
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/EntityFieldGroup.tsx (1)

99-103: Add loading state to the Add button.

The button should show a loading state while an item is being added to prevent duplicate submissions.

       <div className="flex flex-row justify-end">
-        <Button onClick={addItem} disabled={disabled}>
+        <Button 
+          onClick={addItem} 
+          disabled={disabled || items.some(item => !item.__id)} 
+          loading={items.some(item => !item.__id)}
+        >
           {addButtonLabel}
         </Button>
       </div>
packages/ui/src/common/hooks/useHttp/useHttp.unit.test.ts (1)

100-107: Enhance error handling test coverage.

The error test could be more comprehensive by:

  1. Verifying error message content
  2. Testing different types of errors (network, validation, etc.)
  3. Testing error state persistence
 it('should handle error', async () => {
   const mockError = new Error('Test error');
+  const networkError = new Error('Network error');
+  const validationError = new Error('Validation error');
   vi.mocked(request).mockRejectedValueOnce(mockError);
+  vi.mocked(request).mockRejectedValueOnce(networkError);
+  vi.mocked(request).mockRejectedValueOnce(validationError);

   const { result, rerender } = renderHook(() => useHttp(mockParams, mockMetadata));

   await expect(result.current.run()).rejects.toThrow('Test error');
+  expect(result.current.error).toBe(mockError);
+  expect(result.current.error.message).toBe('Test error');
+
+  await expect(result.current.run()).rejects.toThrow('Network error');
+  expect(result.current.error).toBe(networkError);
+
+  await expect(result.current.run()).rejects.toThrow('Validation error');
+  expect(result.current.error).toBe(validationError);

   rerender();

   expect(result.current.error).toBe(mockError);
   expect(result.current.isLoading).toBe(false);
 });
packages/ui/src/components/organisms/Form/DynamicForm/fields/DocumentField/hooks/useDocumentUpload/helpers/get-file-or-fileid-from-documents-list/get-file-or-fileid-from-documents-list.unit.test.ts (1)

7-40: Extract repeated test data into fixtures.

The test data structure is complex and repeated across multiple test cases. Consider:

  1. Creating shared fixtures
  2. Using test data builders
  3. Implementing helper functions for test data creation
// test-fixtures.ts
export const createMockElement = (overrides = {}): IFormElement<'documentfield', IDocumentFieldParams> => ({
  id: 'test-doc',
  element: 'documentfield',
  valueDestination: 'documents',
  params: {
    template: {
      id: 'doc-1',
      // ... default properties
    },
    // ... other default params
  },
  ...overrides,
});

Also applies to: 89-101, 127-147

services/workflows-service/src/workflow/workflow.module.ts (2)

1-2: Address circular dependencies in module imports.

The presence of circular dependencies (// eslint-disable-next-line import/no-cycle) suggests potential architectural issues. Consider:

  1. Extracting shared functionality into a separate module
  2. Using interfaces to break circular dependencies
  3. Implementing the mediator pattern

Also applies to: 6-7


50-112: Consider splitting the WorkflowModule into smaller, focused modules.

The large number of dependencies suggests this module might be handling too many responsibilities. Consider:

  1. Creating separate modules for different concerns (e.g., DocumentModule, NotificationModule)
  2. Using feature modules to group related functionality
  3. Implementing a facade pattern to simplify the module interface
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFieldGroupDocument/EntityFieldGroupDocument.tsx (2)

101-105: Fix typo in warning message.

There's a typo in the console warning message.

-console.warn('Template id is migging in element', element);
+console.warn('Template id is missing in element', element);

39-43: Consider using useLatest hook for values ref.

The current implementation with useRef and useEffect could lead to stale values. Consider using a useLatest hook.

-const valuesRef = useRef(values);
-
-useEffect(() => {
-  valuesRef.current = values;
-}, [values]);
+const valuesRef = useLatest(values);
services/workflows-service/prisma/migrations/20250129151319_documents_project/migration.sql (1)

14-18: Consider using CASCADE delete for Document-Project relationship.

Using RESTRICT on delete might lead to orphaned records. Consider using CASCADE delete if documents should be deleted when their project is deleted.

Apply this diff:

-- AddForeignKey
-ALTER TABLE "Document" ADD CONSTRAINT "Document_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+ALTER TABLE "Document" ADD CONSTRAINT "Document_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cedaeb7 and 8c5ed6a.

📒 Files selected for processing (66)
  • apps/backoffice-v2/src/lib/blocks/hooks/useManageUbosBlock/ubos-form-json-definition.ts (1 hunks)
  • packages/ui/src/common/hooks/useHttp/useHttp.ts (1 hunks)
  • packages/ui/src/common/hooks/useHttp/useHttp.unit.test.ts (1 hunks)
  • packages/ui/src/common/hooks/useHttp/utils/request.ts (1 hunks)
  • packages/ui/src/common/hooks/useHttp/utils/request.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/DocumentField/DocumentField.tsx (2 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/DocumentField/helpers/build-document-form-data/build-document-form-data.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/DocumentField/helpers/build-document-form-data/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/DocumentField/hooks/useDocumentUpload/helpers/create-or-update-fileid-or-file-in-documents/create-or-update-fileid-or-file-in-documents.unit.test.ts (2 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/DocumentField/hooks/useDocumentUpload/helpers/get-file-or-fileid-from-documents-list/get-file-or-fileid-from-documents-list.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/DocumentField/hooks/useDocumentUpload/helpers/get-file-or-fileid-from-documents-list/get-file-or-fileid-from-documents-list.unit.test.ts (5 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/DocumentField/hooks/useDocumentUpload/helpers/remove-document-from-list-by-template-id/remove-document-from-list-by-template-id.unit.test.ts (3 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/DocumentField/hooks/useDocumentUpload/useDocumentUpload.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/EntityFieldGroup.stories.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/EntityFieldGroup.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFieldGroupDocument/EntityFieldGroupDocument.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/EntityFields.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/helpers/build-documents-creation-payload.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/helpers/build-entity-for-creation.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/helpers/update-entities.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/hooks/useIsEntityFieldsValid/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/hooks/useIsEntityFieldsValid/useIsEntityFieldsValid.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/hooks/useEntityFieldGroupList/useEntityFieldGroupList.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/hooks/useEntitySync/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/hooks/useEntitySync/useEntitySync.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldProvider/EntityFieldProvider.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldProvider/entity-field-group-type.context.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldProvider/hooks/external/useEntityField/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldProvider/hooks/external/useEntityField/useEntityField.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldProvider/hooks/external/useEntityFieldGroupType/useEntityFieldGroupType.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldProvider/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldProvider/types.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/types/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/helpers/upload-file/upload-file.unit.test.ts (2 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/hooks/internal/useClear/value-cleaners/documentfield-value-cleaner.unit.test.ts (2 hunks)
  • packages/ui/src/components/organisms/Form/DynamicForm/hooks/internal/useValues/useValues.ts (2 hunks)
  • services/workflows-service/prisma/migrations/20250129142137_documents_init/migration.sql (1 hunks)
  • services/workflows-service/prisma/migrations/20250129151319_documents_project/migration.sql (1 hunks)
  • services/workflows-service/prisma/migrations/20250129160127_documents_issuing_country/migration.sql (1 hunks)
  • services/workflows-service/prisma/migrations/20250202144546_added_document_file_cascade/migration.sql (1 hunks)
  • services/workflows-service/prisma/schema.prisma (6 hunks)
  • services/workflows-service/scripts/generate-end-user.ts (0 hunks)
  • services/workflows-service/src/app.module.ts (2 hunks)
  • services/workflows-service/src/business/business.controller.external.ts (0 hunks)
  • services/workflows-service/src/business/business.controller.ts (0 hunks)
  • services/workflows-service/src/collection-flow/collection-flow-entity.service.ts (1 hunks)
  • services/workflows-service/src/collection-flow/collection-flow.module.ts (4 hunks)
  • services/workflows-service/src/collection-flow/collection-flow.service.ts (0 hunks)
  • services/workflows-service/src/collection-flow/controllers/collection-flow.entity.controller.ts (1 hunks)
  • services/workflows-service/src/collection-flow/controllers/collection-flow.files.controller.ts (5 hunks)
  • services/workflows-service/src/document-file/document-file.module.ts (1 hunks)
  • services/workflows-service/src/document-file/document-file.repository.ts (1 hunks)
  • services/workflows-service/src/document-file/document-file.service.ts (1 hunks)
  • services/workflows-service/src/document-file/dtos/document-file.dto.ts (1 hunks)
  • services/workflows-service/src/document/document.controller.external.ts (1 hunks)
  • services/workflows-service/src/document/document.module.ts (1 hunks)
  • services/workflows-service/src/document/document.repository.ts (1 hunks)
  • services/workflows-service/src/document/document.service.ts (1 hunks)
  • services/workflows-service/src/document/dtos/document.dto.ts (1 hunks)
  • services/workflows-service/src/providers/file/file-service.module.ts (0 hunks)
  • services/workflows-service/src/providers/file/file.module.ts (1 hunks)
  • services/workflows-service/src/providers/file/file.service.ts (3 hunks)
  • services/workflows-service/src/storage/storage.module.ts (1 hunks)
  • services/workflows-service/src/storage/storage.service.ts (2 hunks)
  • services/workflows-service/src/workflow/workflow.module.ts (2 hunks)
  • services/workflows-service/src/workflow/workflow.service.ts (1 hunks)
💤 Files with no reviewable changes (5)
  • services/workflows-service/src/providers/file/file-service.module.ts
  • services/workflows-service/src/business/business.controller.ts
  • services/workflows-service/src/collection-flow/collection-flow.service.ts
  • services/workflows-service/scripts/generate-end-user.ts
  • services/workflows-service/src/business/business.controller.external.ts
✅ Files skipped from review due to trivial changes (5)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/hooks/useEntitySync/index.ts
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldProvider/hooks/external/useEntityField/index.ts
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/DocumentField/helpers/build-document-form-data/index.ts
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/hooks/useIsEntityFieldsValid/index.ts
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldProvider/index.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/types/index.ts
  • services/workflows-service/src/collection-flow/collection-flow.module.ts
  • packages/ui/src/common/hooks/useHttp/useHttp.ts
  • packages/ui/src/common/hooks/useHttp/utils/request.ts
  • packages/ui/src/common/hooks/useHttp/utils/request.unit.test.ts
  • packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/hooks/useEntityFieldGroupList/useEntityFieldGroupList.unit.test.ts
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: test_windows
  • GitHub Check: test_linux
  • GitHub Check: build (windows-latest)
  • GitHub Check: spell_check
  • GitHub Check: build (ubuntu-latest)
  • GitHub Check: lint
🔇 Additional comments (65)
services/workflows-service/prisma/migrations/20250202144546_added_document_file_cascade/migration.sql (4)

1-3: Drop Existing Foreign Key on documentId
The command to drop the foreign key constraint on "documentId" is correctly formatted. Ensure that any existing data integrity issues (e.g., orphan records) are addressed before applying this migration.


4-6: Drop Existing Foreign Key on fileId
The removal of the foreign key constraint on "fileId" is clear and precise. Verify that this constraint removal aligns with the overall changes in document management and that no unintended side effects occur during rollback scenarios.


7-9: Add Foreign Key Constraint on documentId with Cascade Actions
This statement successfully adds a new foreign key constraint on "documentId" with both ON DELETE and ON UPDATE CASCADE. Confirm that the "Document" table’s "id" column is appropriately indexed and that cascading behavior fits the broader business logic.


10-11: Add Foreign Key Constraint on fileId with Cascade Actions
The new foreign key constraint on "fileId" is correctly implemented with cascading updates and deletes referencing the "File" table. It is important to ensure consistency with application expectations and that this cascade behavior does not lead to unintended deletions.

services/workflows-service/src/storage/storage.module.ts (2)

1-1: LGTM! Import statements are correctly structured.

The new imports for forwardRef and FileModule are properly organized and consistent with the existing import style.

Also applies to: 9-9


12-12: 🏁 Script executed:

#!/bin/bash
# Description: Analyze the dependency relationship between StorageModule and FileModule

echo "Checking StorageModule dependencies on FileModule:"
rg -A 5 "FileModule|FileService|FileRepository" "services/workflows-service/src/storage"

echo -e "\nChecking FileModule dependencies on StorageModule:"
rg -A 5 "StorageModule|StorageService" "services/workflows-service/src/providers/file"

Length of output: 6995

services/workflows-service/src/providers/file/file.module.ts (3)

1-8: LGTM! Well-organized imports following NestJS conventions.

The imports are properly structured using absolute paths and include all necessary dependencies for the module's functionality.


12-12: Configure HTTP retry mechanism as noted in TODO.

The HttpModule requires configuration for retry mechanisms. Consider implementing it using HttpModule.registerAsync() with retry options.

Would you like me to provide an example implementation of the HTTP retry configuration?


10-20: Module structure follows NestJS best practices.

The module is well-structured with clear separation of concerns. Note that the empty controllers array might indicate this is still a work in progress, which aligns with the PR's WIP status.

Run the following script to check if there are any controller files that should be included:

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Description: Check for potential controller files that should be included in the module
# Look for controller files in the file provider directory
fd -e ts 'controller.ts$' services/workflows-service/src/providers/file/

Length of output: 74


🏁 Script executed:

#!/bin/bash
# Description: Search recursively for any use of the '@Controller' decorator in the file provider directory.
rg -l '@Controller' services/workflows-service/src/providers/file/

Length of output: 66


Final Verification Outcome: No Controller Files Found

  • The search using both file pattern and '@controller' decorator in the file provider directory returned no results.
  • This confirms that there are no controller files that need to be included in FileModule at this time.
services/workflows-service/src/document-file/dtos/document-file.dto.ts (2)

26-26: LGTM!

The schema correctly omits the 'id' field for create operations, following the standard pattern where IDs are auto-generated.


28-30: LGTM!

The schema correctly:

  • Makes fields optional for partial updates
  • Prevents modification of relationship fields
apps/backoffice-v2/src/lib/blocks/hooks/useManageUbosBlock/ubos-form-json-definition.ts (1)

27-27: Improved naming clarity with UBO-specific terminology.

The change from 'contactIndex' to 'uboIndex' better reflects the form's purpose and maintains consistency with UBO-specific terminology.

Let's verify the terminology consistency across the codebase:

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Search for any remaining instances of 'contactIndex' in translation files and components
rg -i "contactIndex" --type ts --type tsx

# Search for 'uboIndex' usage to ensure consistent terminology
rg -i "uboIndex" --type ts --type tsx

Length of output: 135


🏁 Script executed:

#!/bin/bash
# Search for any remaining instances of 'contactIndex' in TypeScript files (.ts and .tsx)
fd -e ts -e tsx -x grep -Hi "contactIndex"

# Search for any remaining instances of 'uboIndex' in TypeScript files (.ts and .tsx)
fd -e ts -e tsx -x grep -Hi "uboIndex"

Length of output: 451


UBO Naming Standardization Verified

  • The change from "contactIndex" to "uboIndex" was successfully applied with no remaining references to "contactIndex".
  • Both the backoffice-v2 form configuration and the workflows-service UI definition use UBO-specific terminology ("uboIndex"), confirming consistency.
packages/ui/src/components/organisms/Form/DynamicForm/hooks/internal/useValues/useValues.ts (1)

30-30: Good use of shallow copying.

Cloning the entire values object on line 30 helps avoid direct mutations of the previous state. This is a clean approach that prevents unintended side effects.

services/workflows-service/src/providers/file/file.service.ts (1)

13-13: LGTM!

The imports and dependency injection are correctly implemented following NestJS patterns.

Also applies to: 23-24, 32-32

services/workflows-service/src/collection-flow/controllers/collection-flow.files.controller.ts (4)

5-8: Imports look consistent and relevant.
No issues found. Everything appears to be correctly imported to support the new document management functionalities.


12-12: Additional decorators and imports verified.
The addition of WorkflowService, Body, and Delete decorators aligns well with the new endpoints and logic flow.

Also applies to: 14-14, 16-16


40-42: Constructor injection for new services looks good.
No signs of circular dependencies or concurrency pitfalls. Dependency injection is properly set up for file, workflow, and document services.


110-121: Verify behavior when deleting documents.
Ensure you handle edge cases (e.g., empty or invalid IDs arrays) gracefully and confirm the endpoint’s expected behavior if provided with no valid IDs.

services/workflows-service/src/document/document.controller.external.ts (5)

1-35: Initial setup, imports, and constructor look solid.
All decorators and imports (e.g., file interceptors, validation, swagger tags) are properly placed, and the constructor injection is suitable.


109-139: GET documents method is straightforward.
The approach to retrieving documents by entity and workflow IDs is clear and well-validated.


140-167: Update logic appears consistent.
PATCH endpoint with validations is properly set up; no immediate issues observed.


168-223: Reupload endpoint is coherent.
File size and type validations match the create flow, maintaining consistency across document operations.


225-246: Double-check deletions for edge cases.
Similar to the other delete method, confirm how empty arrays or invalid IDs are handled, and whether returning all remaining documents is the intended behavior.

services/workflows-service/src/document/document.service.ts (4)

1-13: Imports are broad and appropriate.
All integrated modules (e.g., repository, file services, workflow) naturally fit the new document service.


15-23: Constructor dependencies appropriately injected.
No cyclical or missing dependencies noted.


138-162: Verify scope of returned documents upon update.
The method returns all documents matching the project criteria, rather than just the updated one(s). Confirm this is the desired feature for client consumption.


164-187: Deletion returns entire document list.
The service removes the specified IDs, then fetches all remaining documents. Verify whether returning just the deleted documents or only the remainder might be a better fit for your client requirements.

packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldProvider/entity-field-group-type.context.ts (1)

1-4: LGTM! Clean context implementation.

The context is properly created following React best practices with appropriate TypeScript typing.

packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldProvider/types.ts (1)

1-6: LGTM! Well-structured interface definition.

The interface is clearly defined with appropriate property requirements.

services/workflows-service/src/document-file/document-file.module.ts (1)

1-11: LGTM!

The module follows NestJS best practices with proper organization of imports, providers, and exports.

packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/providers/EntityFieldProvider/EntityFieldProvider.tsx (1)

1-23: LGTM!

The component follows React best practices with proper use of TypeScript interfaces, context API, and performance optimization using useMemo.

services/workflows-service/src/document/document.module.ts (1)

1-16: LGTM!

The module follows NestJS best practices with proper organization of imports, providers, controllers, and exports.

packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/helpers/build-entity-for-creation.ts (2)

7-20: LGTM! Consider adding type validation.

The implementation is clean and follows good practices by handling both transformed and non-transformed entities. Consider adding runtime type validation for the entity before returning.


18-18: I'll gather more details from the codebase regarding TEntityFieldGroupType to verify its type safety.

packages/ui/src/components/organisms/Form/DynamicForm/fields/DocumentField/hooks/useDocumentUpload/helpers/get-file-or-fileid-from-documents-list/get-file-or-fileid-from-documents-list.ts (1)

14-16: LGTM! Improved readability with explicit return.

The explicit return block improves code readability and maintainability.

services/workflows-service/src/document/dtos/document.dto.ts (1)

20-26: LGTM! Well-designed derived schemas.

The derived schemas follow best practices:

  • CreateDocumentSchema correctly omits auto-generated fields
  • UpdateDocumentSchema allows partial updates
  • DeleteDocumentsSchema is focused and simple
packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFields/hooks/useIsEntityFieldsValid/useIsEntityFieldsValid.ts (1)

9-34: LGTM! Well-structured custom hook.

The implementation is clean and efficient:

  • Good use of useMemo for performance
  • Proper integration with form context
  • Clear transformation of validation schema
services/workflows-service/src/collection-flow/controllers/collection-flow.entity.controller.ts (2)

14-24: Add error handling for invalid token scope.

The method should validate that workflowRuntimeDataId and projectId exist in the token scope before using them.


31-34: Add authorization check and input validation.

The delete operation should:

  1. Verify that the user has permission to delete the entity.
  2. Validate the entityId format.
services/workflows-service/src/document/document.repository.ts (1)

1-86: 🏁 Script executed:

#!/bin/bash
# Search for error handling patterns in repository classes
rg -A 5 "catch|throw|Error|Exception" "services/**/repository.ts"

Length of output: 133

packages/ui/src/components/organisms/Form/DynamicForm/helpers/upload-file/upload-file.unit.test.ts (1)

19-25: LGTM! Good test coverage for error handling.

The test case properly validates the error handling when upload settings are missing, with appropriate type casting.

services/workflows-service/src/collection-flow/collection-flow-entity.service.ts (1)

1-15: LGTM! Well-structured service class with proper dependency injection.

The service follows NestJS best practices with proper dependency injection and protected service members.

packages/ui/src/common/hooks/useHttp/useHttp.unit.test.ts (1)

114-114: Consider using the common sleep utility.

Replace the inline setTimeout with the common sleep utility from the shared codebase.

packages/ui/src/components/organisms/Form/DynamicForm/fields/DocumentField/DocumentField.tsx (1)

23-34: LGTM! Well-structured interface definition.

The IDocumentTemplate interface provides good type safety with clear property definitions.

services/workflows-service/src/app.module.ts (1)

52-52: LGTM! Clean module integration.

The DocumentModule is properly imported and added to the module imports array following NestJS best practices.

Also applies to: 137-137

packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/components/EntityFieldGroupDocument/EntityFieldGroupDocument.tsx (1)

127-143: 🛠️ Refactor suggestion

Add file validation in handleChange.

The handleChange function assumes a file will always be present. Add proper validation.

 const handleChange = useCallback(
   (e: React.ChangeEvent<HTMLInputElement>) => {
+    if (!e.target.files?.length) {
+      return;
+    }
+    const file = e.target.files[0];
+    if (!file) {
+      return;
+    }
     const documents = get(documentsList || [], element.valueDestination);
     const updatedDocuments = createOrUpdateFileIdOrFileInDocuments(
       documents,
       element,
-      e.target.files?.[0] as File,
+      file,
     );

Likely invalid or redundant comment.

packages/ui/src/components/organisms/Form/DynamicForm/fields/EntityFieldGroup/EntityFieldGroup.stories.tsx (5)

103-105: Remove console.log statements.

Production code should not contain console.log statements.

Also applies to: 506-507, 623-624


12-92: Reduce code duplication in schema definitions.

The schema definitions for default, ubos, and directors are nearly identical, differing only in the type parameter.

Also applies to: 125-475, 531-611


94-115: Extract common layout and behavior into a reusable component.

The EntityFieldGroup, UbosFieldGroup, and DirectorsFieldGroup components share identical layout and behavior.

Also applies to: 496-525, 613-634


510-516: Apply consistent validation configuration across all form components.

The UbosFieldGroup has detailed validation configuration that should be applied to all form components for consistent behavior.


490-494: ⚠️ Potential issue

Remove hardcoded API configuration.

The metadata object contains hardcoded API URL and token which is a security risk. Consider using environment variables or a configuration service.

Apply this diff:

-const metadata = {
-  apiUrl: 'http://localhost:3000/api/v1/',
-  token: 'e3a69aa3-c1ad-42f3-87ac-5105cff81a94',
-  workflowId: 'cm6ufmpme0004tl7sqoxwlah4',
-};
+const metadata = {
+  apiUrl: process.env.REACT_APP_API_URL,
+  token: process.env.REACT_APP_API_TOKEN,
+  workflowId: process.env.REACT_APP_WORKFLOW_ID,
+};

Likely invalid or redundant comment.

services/workflows-service/prisma/migrations/20250129142137_documents_init/migration.sql (7)

1-6: Data Loss Warning & Backup Reminder:
The comment block clearly warns that the "documents" column on the "Business" table will be dropped and its data lost. Please ensure that a robust backup and data migration plan is in place before executing this migration.


7-18: Enum Definitions for Document Management:
The ENUM types ("DocumentStatus", "DocumentDecision", "DocumentFileType", and "DocumentFileVariant") are defined to cover the expected document states and file characteristics. Verify that these values fully encompass all the business cases, and consider adding comments if additional context is needed.


23-39: Document Table Creation:
The "Document" table is created with clearly defined fields, a primary key, and timestamp management. Confirm that the chosen data types (e.g., TEXT for id and other fields) and the DEFAULT for "createdAt" align with your application’s timezone and localization strategies.


42-51: DocumentFile Table Creation:
The "DocumentFile" table is correctly defined with necessary fields and a primary key. Ensure that the field types and constraints fit the use-cases for document file management.


53-67: Index Creation for Performance Optimization:
Indexes on key columns (e.g., "businessId", "endUserId", "workflowRuntimeDataId" for Document, and "documentId", "fileId" for DocumentFile) are set up to boost query performance. Verify that these indexes are optimal for the most common query patterns executed by the application.


68-81: Foreign Key Constraints & Cascade Rules:
Foreign key constraints are added to link "Document" and "DocumentFile" with related tables. Notably, the "Document" table uses "ON DELETE SET NULL" while "DocumentFile" employs "ON DELETE RESTRICT". Double-check that these on-delete actions accurately reflect the intended data integrity and business logic for deletions.


19-20: ⚠️ Potential issue

Critical Column Drop Operation:
Dropping the "documents" column from the "Business" table is irreversible. Ensure that this decision has been fully vetted and that any necessary backups or data migrations have been completed prior to running this change.

services/workflows-service/prisma/schema.prisma (7)

100-101: EndUser Model Update – Document Relationship:
A new field "documents Document[]" has been added to the EndUser model to establish a one-to-many relation with Document. Ensure that the application logic properly leverages this relationship and that querying on documents is efficient.


152-152: Business Model Update – Document Collection:
The Business model now includes a "documents Document[]" field to handle document collections (e.g., KYB process files). Confirm that the migration from any legacy "documents" JSON field to this new relational mapping has been synchronized between the database and the Prisma schema.


317-318: File Model Enhancement – Tracking Document Files:
The addition of "documentFiles DocumentFile[]" in the File model supports direct association with document files. Please review whether the cascade and update rules for these relations are consistent with intended deletion or update behaviors in the application.


426-427: Project Model Enhancements – Aggregating Documents and Files:
New fields "documents Document[]" and "documentFiles DocumentFile[]" in the Project model will help correlate document management with project scope. Consider whether additional indexing is necessary on these relations to optimize performance during project-level queries.


1000-1023: Document-Related Enum Definitions:
The enums ("DocumentStatus", "DocumentDecision", "DocumentFileType", and "DocumentFileVariant") are re-declared in the Prisma schema. Verify that these definitions remain synchronized with the migration script and the domain logic throughout the application.


965-998: ⚠️ Potential issue

Document Model Definition & Consistency Check:
The new Document model is defined with fields such as "issuingVersion" and "issuingCountry" among others. Note that while the Prisma model includes an "issuingCountry" field, the corresponding migration script does not include this column. Ensure that your database migration and Prisma schema are consistent to avoid runtime discrepancies.


1024-1041: ⚠️ Potential issue

DocumentFile Model & Foreign Key Deletion Policy:
In the DocumentFile model, the relations to Document and File use "onDelete: Cascade", which differs from the "ON DELETE RESTRICT" specified in the migration script. Reconcile these differences to ensure that the ORM behavior aligns with the database constraints and that cascade deletions are intentional.

Comment on lines +15 to +24
export const DocumentFileJsonSchema = z
.string()
.transform(value => JSON.parse(value))
.pipe(
z.object({
type: z.nativeEnum(DocumentFileType),
variant: z.nativeEnum(DocumentFileVariant),
page: z.number().positive().int(),
}),
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for JSON parsing.

The current implementation might throw uncaught exceptions during JSON parsing. Consider adding proper error handling:

 export const DocumentFileJsonSchema = z
   .string()
-  .transform(value => JSON.parse(value))
+  .transform((value, ctx) => {
+    try {
+      return JSON.parse(value);
+    } catch (e) {
+      ctx.addIssue({
+        code: z.ZodIssueCode.custom,
+        message: 'Invalid JSON string',
+      });
+      return z.NEVER;
+    }
+  })
   .pipe(
     z.object({
       type: z.nativeEnum(DocumentFileType),
       variant: z.nativeEnum(DocumentFileVariant),
       page: z.number().positive().int(),
     }),
   );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const DocumentFileJsonSchema = z
.string()
.transform(value => JSON.parse(value))
.pipe(
z.object({
type: z.nativeEnum(DocumentFileType),
variant: z.nativeEnum(DocumentFileVariant),
page: z.number().positive().int(),
}),
);
export const DocumentFileJsonSchema = z
.string()
.transform((value, ctx) => {
try {
return JSON.parse(value);
} catch (e) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Invalid JSON string',
});
return z.NEVER;
}
})
.pipe(
z.object({
type: z.nativeEnum(DocumentFileType),
variant: z.nativeEnum(DocumentFileVariant),
page: z.number().positive().int(),
}),
);

Comment on lines +34 to +47
if (parentValueDestination) {
const parentValue = get(prev, parentValueDestination);
let newParentValue: any;

if (Array.isArray(parentValue)) {
newParentValue = [...parentValue];
}

if (typeof parentValue === 'object' && !Array.isArray(parentValue)) {
newParentValue = { ...parentValue };
}

set(newValues, parentValueDestination, newParentValue);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add null/undefined checks for the parent value.

Currently, if the parent value is null or undefined, it falls outside the array/object checks, leaving the code to overwrite it with undefined. This might unintentionally remove data. As a remedy, consider adding explicit checks for null or undefined.

You could apply a diff like:

if (parentValueDestination) {
  const parentValue = get(prev, parentValueDestination);
  let newParentValue: any;

+ if (parentValue == null) {
+   // Protect against null/undefined by returning early or initializing a default structure
+   return newValues;
+ }

  if (Array.isArray(parentValue)) {
    newParentValue = [...parentValue];
  } else if (typeof parentValue === 'object') {
    newParentValue = { ...parentValue };
  }

  set(newValues, parentValueDestination, newParentValue);
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (parentValueDestination) {
const parentValue = get(prev, parentValueDestination);
let newParentValue: any;
if (Array.isArray(parentValue)) {
newParentValue = [...parentValue];
}
if (typeof parentValue === 'object' && !Array.isArray(parentValue)) {
newParentValue = { ...parentValue };
}
set(newValues, parentValueDestination, newParentValue);
}
if (parentValueDestination) {
const parentValue = get(prev, parentValueDestination);
let newParentValue: any;
if (parentValue == null) {
// Protect against null/undefined by returning early or initializing a default structure
return newValues;
}
if (Array.isArray(parentValue)) {
newParentValue = [...parentValue];
} else if (typeof parentValue === 'object') {
newParentValue = { ...parentValue };
}
set(newValues, parentValueDestination, newParentValue);
}

Comment on lines +377 to +379
const nameWithoutExtension = (file.originalname || randomUUID()).replace(/\.[^.]+$/, '');
// Remove non characters
const alphabeticOnlyName = nameWithoutExtension.replace(/\W/g, '');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance file name sanitization.

The current implementation has potential issues:

  • Removing all non-word characters could result in empty names
  • No length validation
  • Potential name collisions

Consider this more robust implementation:

-    // Remove file extension (get everything before the last dot)
-    const nameWithoutExtension = (file.originalname || randomUUID()).replace(/\.[^.]+$/, '');
-    // Remove non characters
-    const alphabeticOnlyName = nameWithoutExtension.replace(/\W/g, '');
+    const sanitizeFileName = (fileName: string): string => {
+      // Use UUID if filename is empty
+      const baseFileName = fileName || randomUUID();
+      // Remove file extension
+      const nameWithoutExtension = baseFileName.replace(/\.[^.]+$/, '');
+      // Replace non-word chars with hyphens and trim
+      const sanitized = nameWithoutExtension
+        .replace(/\W+/g, '-')
+        .replace(/^-+|-+$/g, '');
+      // Use UUID if sanitized name is empty
+      return sanitized || randomUUID();
+    };
+
+    const alphabeticOnlyName = sanitizeFileName(file.originalname);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const nameWithoutExtension = (file.originalname || randomUUID()).replace(/\.[^.]+$/, '');
// Remove non characters
const alphabeticOnlyName = nameWithoutExtension.replace(/\W/g, '');
const sanitizeFileName = (fileName: string): string => {
// Use UUID if filename is empty
const baseFileName = fileName || randomUUID();
// Remove file extension
const nameWithoutExtension = baseFileName.replace(/\.[^.]+$/, '');
// Replace non-word chars with hyphens and trim
const sanitized = nameWithoutExtension
.replace(/\W+/g, '-')
.replace(/^-+|-+$/g, '');
// Use UUID if sanitized name is empty
return sanitized || randomUUID();
};
const alphabeticOnlyName = sanitizeFileName(file.originalname);

Comment on lines +362 to +366
async uploadNewFile(
projectId: string,
workflowRuntimeData: WorkflowRuntimeData,
file: Express.Multer.File,
) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add file validation.

The method lacks validation for file type and size.

Consider adding these validations:

+  private validateFile(file: Express.Multer.File) {
+    const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
+    const ALLOWED_MIME_TYPES = ['image/jpeg', 'image/png', 'application/pdf'];
+
+    if (file.size > MAX_FILE_SIZE) {
+      throw new BadRequestException('File size exceeds the limit of 10MB');
+    }
+
+    if (!ALLOWED_MIME_TYPES.includes(file.mimetype)) {
+      throw new BadRequestException('Invalid file type. Allowed types: JPEG, PNG, PDF');
+    }
+  }

  async uploadNewFile(
    projectId: string,
    workflowRuntimeData: WorkflowRuntimeData,
    file: Express.Multer.File,
+  ) {
+    this.validateFile(file);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async uploadNewFile(
projectId: string,
workflowRuntimeData: WorkflowRuntimeData,
file: Express.Multer.File,
) {
private validateFile(file: Express.Multer.File) {
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
const ALLOWED_MIME_TYPES = ['image/jpeg', 'image/png', 'application/pdf'];
if (file.size > MAX_FILE_SIZE) {
throw new BadRequestException('File size exceeds the limit of 10MB');
}
if (!ALLOWED_MIME_TYPES.includes(file.mimetype)) {
throw new BadRequestException('Invalid file type. Allowed types: JPEG, PNG, PDF');
}
}
async uploadNewFile(
projectId: string,
workflowRuntimeData: WorkflowRuntimeData,
file: Express.Multer.File,
) {
this.validateFile(file);
// ... the rest of the method implementation


const filePath = composePathToFileId(documentIndex, pageProperty, pageIndex);
console.log('filePath', filePath);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove console.log statement.

Console.log statements should not be committed to production code. Consider using a proper logging system if debugging information is needed.

-  console.log('filePath', filePath);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log('filePath', filePath);

resultPath: '',
},
},
} as unknown as IDocumentFieldParams,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid type casting with 'as unknown as'.

The use of type casting suggests potential type definition issues. Consider:

  1. Properly typing the mock data
  2. Using partial types for test data
  3. Creating proper type constructors
-} as unknown as IDocumentFieldParams,
+}: Partial<IDocumentFieldParams> = {
+  template: {
+    id: 'doc-1',
+    // ... other properties
+  },
+};

Also applies to: 101-101, 147-147

Comment on lines +36 to 46
export interface IDocumentFieldParams extends IFileFieldParams {
template: IDocumentTemplate;
pageIndex?: number;
pageProperty?: string;
documentType: string;
documentVariant: string;
httpParams: {
createDocument: IHttpParams;
deleteDocument: IHttpParams;
};
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify HTTP parameters before usage.

The httpParams property contains critical API endpoints for document operations. Ensure these are validated before use.


🏁 Script executed:

#!/bin/bash
# Description: Check if the HTTP parameters are properly validated before use

# Search for validation of httpParams
rg -A 5 'httpParams\?' .

Length of output: 7452


HTTP Parameters in DocumentField.tsx Lack Explicit Runtime Validation
The interface in DocumentField.tsx only defines the expected structure for the HTTP parameters at compile time. Our repository-wide search confirms that, like most other components, DocumentField relies on TypeScript’s type checks and optional chaining rather than performing explicit runtime validation of the API endpoints before they’re used. Consider adding explicit checks (or leveraging a validation hook) to ensure that the critical endpoints in httpParams—namely createDocument and deleteDocument—are correctly provided and formatted before any API calls are made.

  • Verify that runtime validation is in place (or add it) before these endpoints are used.
  • Align DocumentField’s behavior with similar components if a consistent validation strategy is preferred.

Comment on lines +63 to +124
const createEntityAndUploadDocuments = useCallback(async () => {
setIsCreatingEntity(true);

const context = values;

const entitiesDestination = formatValueDestination(element.valueDestination, stack);

const createEntityPayload = await buildEntityCreationPayload(element, entity, context);

let createdEntityId: string;

try {
createdEntityId = await createEntity(createEntityPayload);
} catch (error) {
console.error(error);
toast.error('Failed to create entity.');
setIsCreatingEntity(false);
throw error;
}

const entities = get(context, entitiesDestination, []);
const createdEntity = { ...entity, id: createdEntityId };

// UI Update
const updatedEntities = updateEntities(entities, createdEntity);
set(context, entitiesDestination, updatedEntities);

const documentsCreationPayload = await buildDocumentsCreationPayload(element, context, {
entityId: createdEntityId,
stack: stack,
});

const documentUploadPromises = documentsCreationPayload.map(async document => {
const documentId = await uploadDocument(document.payload);

const updatedDocuments = createOrUpdateFileIdOrFileInDocuments(
get(context, document.valueDestination, []),
document.documentDefinition,
documentId,
);

set(context, document.valueDestination, updatedDocuments);

return documentId;
});

try {
await Promise.all(documentUploadPromises);

onChange(updatedEntities);
} catch (error) {
console.error(error);

toast.error('Failed to upload documents.');
setIsCreatingEntity(false);
throw error;
}

setIsCreatingEntity(false);

toast.success('Entity created successfully.');
}, [stack, element, values, createEntity, uploadDocument, entity, onChange]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve error handling in entity creation flow.

The createEntityAndUploadDocuments function has several areas for improvement:

  1. Consider using Promise.allSettled instead of Promise.all to handle partial failures
  2. Move success toast after all operations complete
  3. Add retry logic for failed document uploads
 try {
-  await Promise.all(documentUploadPromises);
+  const results = await Promise.allSettled(documentUploadPromises);
+  const failures = results.filter(r => r.status === 'rejected');
+  
+  if (failures.length > 0) {
+    console.error('Some documents failed to upload:', failures);
+    toast.error(`${failures.length} documents failed to upload.`);
+  }
 
   onChange(updatedEntities);
+  toast.success('Entity created successfully.');
 } catch (error) {
   console.error(error);
   toast.error('Failed to upload documents.');
   setIsCreatingEntity(false);
   throw error;
 }
 
 setIsCreatingEntity(false);
-
-toast.success('Entity created successfully.');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const createEntityAndUploadDocuments = useCallback(async () => {
setIsCreatingEntity(true);
const context = values;
const entitiesDestination = formatValueDestination(element.valueDestination, stack);
const createEntityPayload = await buildEntityCreationPayload(element, entity, context);
let createdEntityId: string;
try {
createdEntityId = await createEntity(createEntityPayload);
} catch (error) {
console.error(error);
toast.error('Failed to create entity.');
setIsCreatingEntity(false);
throw error;
}
const entities = get(context, entitiesDestination, []);
const createdEntity = { ...entity, id: createdEntityId };
// UI Update
const updatedEntities = updateEntities(entities, createdEntity);
set(context, entitiesDestination, updatedEntities);
const documentsCreationPayload = await buildDocumentsCreationPayload(element, context, {
entityId: createdEntityId,
stack: stack,
});
const documentUploadPromises = documentsCreationPayload.map(async document => {
const documentId = await uploadDocument(document.payload);
const updatedDocuments = createOrUpdateFileIdOrFileInDocuments(
get(context, document.valueDestination, []),
document.documentDefinition,
documentId,
);
set(context, document.valueDestination, updatedDocuments);
return documentId;
});
try {
await Promise.all(documentUploadPromises);
onChange(updatedEntities);
} catch (error) {
console.error(error);
toast.error('Failed to upload documents.');
setIsCreatingEntity(false);
throw error;
}
setIsCreatingEntity(false);
toast.success('Entity created successfully.');
}, [stack, element, values, createEntity, uploadDocument, entity, onChange]);
const createEntityAndUploadDocuments = useCallback(async () => {
setIsCreatingEntity(true);
const context = values;
const entitiesDestination = formatValueDestination(element.valueDestination, stack);
const createEntityPayload = await buildEntityCreationPayload(element, entity, context);
let createdEntityId: string;
try {
createdEntityId = await createEntity(createEntityPayload);
} catch (error) {
console.error(error);
toast.error('Failed to create entity.');
setIsCreatingEntity(false);
throw error;
}
const entities = get(context, entitiesDestination, []);
const createdEntity = { ...entity, id: createdEntityId };
// UI Update
const updatedEntities = updateEntities(entities, createdEntity);
set(context, entitiesDestination, updatedEntities);
const documentsCreationPayload = await buildDocumentsCreationPayload(element, context, {
entityId: createdEntityId,
stack: stack,
});
const documentUploadPromises = documentsCreationPayload.map(async document => {
const documentId = await uploadDocument(document.payload);
const updatedDocuments = createOrUpdateFileIdOrFileInDocuments(
get(context, document.valueDestination, []),
document.documentDefinition,
documentId,
);
set(context, document.valueDestination, updatedDocuments);
return documentId;
});
try {
- await Promise.all(documentUploadPromises);
+ const results = await Promise.allSettled(documentUploadPromises);
+ const failures = results.filter(r => r.status === 'rejected');
+
+ if (failures.length > 0) {
+ console.error('Some documents failed to upload:', failures);
+ toast.error(`${failures.length} documents failed to upload.`);
+ }
onChange(updatedEntities);
+ toast.success('Entity created successfully.');
} catch (error) {
console.error(error);
toast.error('Failed to upload documents.');
setIsCreatingEntity(false);
throw error;
}
setIsCreatingEntity(false);
- toast.success('Entity created successfully.');
}, [stack, element, values, createEntity, uploadDocument, entity, onChange]);

Comment on lines +25 to 26
// eslint-disable-next-line import/no-cycle
import { FileService } from '@/providers/file/file.service';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fix the cyclic dependency between modules.

Disabling the ESLint cycle detection is not a good practice as it could lead to runtime issues. Consider:

  1. Restructuring the modules to break the cycle
  2. Moving shared code to a common module
  3. Using dependency injection or an event system

Comment on lines +8 to +12
-- AlterTable
ALTER TABLE "Document" ADD COLUMN "projectId" TEXT NOT NULL;

-- AlterTable
ALTER TABLE "DocumentFile" ADD COLUMN "projectId" TEXT NOT NULL;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Provide a default value or migration strategy for existing data.

Adding required columns without default values will fail if the tables contain data. Consider implementing a data migration strategy.

Apply this diff:

-- AlterTable
-ALTER TABLE "Document" ADD COLUMN     "projectId" TEXT NOT NULL;
+-- First add the column as nullable
+ALTER TABLE "Document" ADD COLUMN "projectId" TEXT;
+
+-- Add a default project ID for existing records
+UPDATE "Document" SET "projectId" = 'default_project' WHERE "projectId" IS NULL;
+
+-- Then make it non-nullable
+ALTER TABLE "Document" ALTER COLUMN "projectId" SET NOT NULL;

-- AlterTable
-ALTER TABLE "DocumentFile" ADD COLUMN     "projectId" TEXT NOT NULL;
+-- First add the column as nullable
+ALTER TABLE "DocumentFile" ADD COLUMN "projectId" TEXT;
+
+-- Add a default project ID for existing records
+UPDATE "DocumentFile" SET "projectId" = 'default_project' WHERE "projectId" IS NULL;
+
+-- Then make it non-nullable
+ALTER TABLE "DocumentFile" ALTER COLUMN "projectId" SET NOT NULL;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
-- AlterTable
ALTER TABLE "Document" ADD COLUMN "projectId" TEXT NOT NULL;
-- AlterTable
ALTER TABLE "DocumentFile" ADD COLUMN "projectId" TEXT NOT NULL;
-- AlterTable
-- First add the column as nullable
ALTER TABLE "Document" ADD COLUMN "projectId" TEXT;
-- Add a default project ID for existing records
UPDATE "Document" SET "projectId" = 'default_project' WHERE "projectId" IS NULL;
-- Then make it non-nullable
ALTER TABLE "Document" ALTER COLUMN "projectId" SET NOT NULL;
-- AlterTable
-- First add the column as nullable
ALTER TABLE "DocumentFile" ADD COLUMN "projectId" TEXT;
-- Add a default project ID for existing records
UPDATE "DocumentFile" SET "projectId" = 'default_project' WHERE "projectId" IS NULL;
-- Then make it non-nullable
ALTER TABLE "DocumentFile" ALTER COLUMN "projectId" SET NOT NULL;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants